@a2f0/skyline 0.2.26 → 0.2.28
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.
- package/docs/skyline-geography.md +8 -3
- package/lib/models/colour-palette.d.ts +3 -2
- package/lib/models/colour-palette.js +3 -2
- package/lib/models/colour-reflections.js +9 -4
- package/lib/models/night-windows.js +9 -1
- package/lib/models/sheraton-grand-geographic.d.ts +2 -0
- package/lib/models/sheraton-grand-geographic.js +46 -9
- package/lib/models/skyline-geography-data.d.ts +1 -0
- package/lib/models/skyline-geography-data.js +3 -2
- package/lib/models/skyline-geography.d.ts +7 -0
- package/lib/models/skyline-geography.js +31 -2
- package/package.json +1 -1
- package/site/building-detail.js +7 -4
- package/site/models/colour-palette.js +3 -2
- package/site/models/colour-reflections.js +9 -4
- package/site/models/night-windows.js +9 -1
- package/site/models/sheraton-grand-geographic.js +46 -9
- package/site/models/skyline-geography-data.js +3 -2
- package/site/models/skyline-geography.js +31 -2
- package/site/skyline-3d.html +2 -2
- package/site/skyline-comparison.js +10 -2
|
@@ -133,7 +133,10 @@ Tower's part were added on September 29, 2026, from the same request, and the Sh
|
|
|
133
133
|
Chicago Riverwalk's tower and corner parts the same day, from
|
|
134
134
|
[a fifth](https://api.openstreetmap.org/api/0.6/map?bbox=-87.6250,41.8860,-87.6050,41.8980),
|
|
135
135
|
and Three Illinois Center's outline and the Swissôtel's tower part the same day, from the
|
|
136
|
-
fourth.
|
|
136
|
+
fourth. On 2026-10-17, the Sheraton east wing and round end were retrieved from
|
|
137
|
+
[relation 17432075](https://api.openstreetmap.org/api/0.6/relation/17432075/full.json)
|
|
138
|
+
and [way 1269924304](https://api.openstreetmap.org/api/0.6/way/1269924304/full.json);
|
|
139
|
+
its combined ground coverage names the tower and wing ways separately.
|
|
137
140
|
It includes the selected building outlines and parts. The streets come from a separate
|
|
138
141
|
extract retrieved on September 30, 2026 through the
|
|
139
142
|
[Overpass API](https://overpass-api.de/api/interpreter?data=%5Bout%3Ajson%5D%3Bway%5B%22highway%22%5D%2841.8765%2C-87.6310%2C41.8916%2C-87.6158%29%3Bout%20meta%20geom%3B),
|
|
@@ -200,7 +203,7 @@ of its tower. The cyan ground outlines show that distinction.
|
|
|
200
203
|
| Two Illinois Center | [236770799](https://www.openstreetmap.org/way/236770799) | Outline, with the floor pitch from the photograph; not in the original layout |
|
|
201
204
|
| River Plaza | [285867424](https://www.openstreetmap.org/way/285867424) | 68796725, 68796733, 285867425; not in the original layout |
|
|
202
205
|
| Hyatt Regency West Tower | [235920252](https://www.openstreetmap.org/way/235920252) | The tower's part of the hotel's outline, 235920251; not in the original layout |
|
|
203
|
-
| Sheraton Grand | [592122464](https://www.openstreetmap.org/way/592122464) | The tower's part of the complex's outline, 188171430,
|
|
206
|
+
| Sheraton Grand | [592122464](https://www.openstreetmap.org/way/592122464) | The tower's part of the complex's outline, 188171430, the corner 1269924307, east wing 1269924305 and round end 1269924304; not in the original layout |
|
|
204
207
|
| Three Illinois Center | [95486958](https://www.openstreetmap.org/way/95486958) | Outline; not in the original layout |
|
|
205
208
|
| Swissôtel | [641288601](https://www.openstreetmap.org/way/641288601) | The tower's part of the hotel's outline, 95486966; not in the original layout |
|
|
206
209
|
|
|
@@ -461,7 +464,9 @@ Intermediate parts have less certainty than the overall height:
|
|
|
461
464
|
the drawing paints a lit crown. Its levels stand on the Skyscraper Center's datum, with
|
|
462
465
|
Emporis's floor-to-floor, roof and crown heights over its top floor: the arms' roof at
|
|
463
466
|
94.76 m, the corner's two floors higher at 100.1 m, and 12.2 m drums of maroon fins on the
|
|
464
|
-
three round ends, the corner's to the published 112.3 m. The
|
|
467
|
+
original three round ends, the corner's to the published 112.3 m. The 2026-10-17
|
|
468
|
+
pass restores the lower east wing at an estimated 84.08 m and its round end at
|
|
469
|
+
86.75 m, provisionally sharing the crown assembly pending finer measurements. The Skyscraper Center's tip, 50 cm
|
|
465
470
|
higher, names no feature the photographs resolve, so the model stops at the drum's top; see
|
|
466
471
|
the [Sheraton Grand reference audit](sheraton-grand-reference.md).
|
|
467
472
|
- Three Illinois Center stands between 340 on the Park and The Buckingham from the drawing's
|
|
@@ -9,8 +9,9 @@ export declare const daylightColours: {
|
|
|
9
9
|
export declare const nightColours: {
|
|
10
10
|
readonly ambient: 10596296;
|
|
11
11
|
readonly fill: 9281469;
|
|
12
|
-
readonly window:
|
|
13
|
-
readonly windowWhite:
|
|
12
|
+
readonly window: 16761213;
|
|
13
|
+
readonly windowWhite: 16772570;
|
|
14
|
+
readonly windowCool: 14478079;
|
|
14
15
|
};
|
|
15
16
|
export declare const timeOfDayColours: readonly [{
|
|
16
17
|
readonly elevation: -18;
|
|
@@ -290,8 +290,9 @@ export const measuredColours = {
|
|
|
290
290
|
// above the horizon, each the median of a clear box of the same panorama (scripts/sample-colours.ts,
|
|
291
291
|
// the day study's sky), and the sun's warm white, chosen.
|
|
292
292
|
export const daylightColours = { sky: 0x9ebbdb, horizon: 0xc4d1e1, sun: 0xfff4e6 };
|
|
293
|
-
//
|
|
294
|
-
|
|
293
|
+
// Illustrated warm, neutral and cool white fixtures; not calibrated Kelvin values.
|
|
294
|
+
// DOE's white-tuning overview informs the range; see docs/viewer.md (time of day).
|
|
295
|
+
export const nightColours = { ambient: 0xa1afc8, fill: 0x8d9fbd, window: 0xffc17d, windowWhite: 0xffedda, windowCool: 0xdceaff };
|
|
295
296
|
// Illustrated clear-sky time previews. Solar elevation chooses the stops; these are
|
|
296
297
|
// artistic colours, separate from the photograph-calibrated daytime palette above.
|
|
297
298
|
export const timeOfDayColours = [
|
|
@@ -22,6 +22,7 @@ export function glassReflection(material, geometry, mask, windows = new Float32A
|
|
|
22
22
|
skylineWindowsSuppressed: { value: 0 },
|
|
23
23
|
skylineWindowWarm: { value: new THREE.Color(nightColours.window) },
|
|
24
24
|
skylineWindowWhite: { value: new THREE.Color(nightColours.windowWhite) },
|
|
25
|
+
skylineWindowCool: { value: new THREE.Color(nightColours.windowCool) },
|
|
25
26
|
};
|
|
26
27
|
const compile = (shader, renderer) => {
|
|
27
28
|
originalCompile.call(material, shader, renderer);
|
|
@@ -44,7 +45,8 @@ uniform float skylineDarkness;
|
|
|
44
45
|
uniform float skylineActivity;
|
|
45
46
|
uniform float skylineWindowsSuppressed;
|
|
46
47
|
uniform vec3 skylineWindowWarm;
|
|
47
|
-
uniform vec3 skylineWindowWhite
|
|
48
|
+
uniform vec3 skylineWindowWhite;
|
|
49
|
+
uniform vec3 skylineWindowCool;`).replace("#include <opaque_fragment>", `
|
|
48
50
|
if (vSkylineGlass > 0.0 && skylineReflecting > 0.0) {
|
|
49
51
|
vec3 eye = vec3(0.0);
|
|
50
52
|
eye.z = 1.0;
|
|
@@ -59,7 +61,10 @@ if (vSkylineWindow.z > 0.0 && skylineDarkness > 0.0) {
|
|
|
59
61
|
float occupied = 1.0 - smoothstep(skylineActivity - 0.05, skylineActivity + 0.05, vSkylineWindow.x);
|
|
60
62
|
float dusk = smoothstep(0.12 + 0.35 * vSkylineWindow.x, 0.45 + 0.35 * vSkylineWindow.x, skylineDarkness);
|
|
61
63
|
float light = vSkylineWindow.z > 1.5 ? skylineDarkness : occupied * dusk * (1.0 - skylineWindowsSuppressed);
|
|
62
|
-
|
|
64
|
+
float tint = vSkylineWindow.w;
|
|
65
|
+
vec3 lamp = tint < 0.5
|
|
66
|
+
? mix(skylineWindowWarm, skylineWindowWhite, tint * 2.0)
|
|
67
|
+
: mix(skylineWindowWhite, skylineWindowCool, (tint - 0.5) * 2.0);
|
|
63
68
|
outgoingLight += lamp * vSkylineWindow.y * light;
|
|
64
69
|
}
|
|
65
70
|
#include <opaque_fragment>`);
|
|
@@ -74,7 +79,7 @@ if (vSkylineWindow.z > 0.0 && skylineDarkness > 0.0) {
|
|
|
74
79
|
geometry.setAttribute("skylineGlass", attribute);
|
|
75
80
|
geometry.setAttribute("skylineWindow", windowAttribute);
|
|
76
81
|
material.onBeforeCompile = compile;
|
|
77
|
-
material.customProgramCacheKey = () => `${key}:skyline-facade-
|
|
82
|
+
material.customProgramCacheKey = () => `${key}:skyline-facade-v3`;
|
|
78
83
|
}
|
|
79
84
|
else {
|
|
80
85
|
material.onBeforeCompile = originalCompile;
|
|
@@ -99,7 +104,7 @@ if (vSkylineWindow.z > 0.0 && skylineDarkness > 0.0) {
|
|
|
99
104
|
uniforms.skylineDarkness.value = lighting?.darkness ?? 0;
|
|
100
105
|
uniforms.skylineWindowsSuppressed.value = celebration ? 1 : 0;
|
|
101
106
|
uniforms.skylineActivity.value = lighting?.activity ?? 0;
|
|
102
|
-
for (const [uniform, hex] of [[uniforms.skylineWindowWarm, nightColours.window], [uniforms.skylineWindowWhite, nightColours.windowWhite]]) {
|
|
107
|
+
for (const [uniform, hex] of [[uniforms.skylineWindowWarm, nightColours.window], [uniforms.skylineWindowWhite, nightColours.windowWhite], [uniforms.skylineWindowCool, nightColours.windowCool]]) {
|
|
103
108
|
uniform.value.setHex(hex);
|
|
104
109
|
if (!coloured) {
|
|
105
110
|
const { r, g, b } = uniform.value, grey = r * 0.2126 + g * 0.7152 + b * 0.0722;
|
|
@@ -29,6 +29,11 @@ const architectural = new Set(["lamp", "crown lights", "band lights", "sign lett
|
|
|
29
29
|
export function nightWindowMask(geometry, buildingId, materialOf) {
|
|
30
30
|
const positions = geometry.getAttribute("position"), normals = geometry.getAttribute("normal");
|
|
31
31
|
const mask = new Float32Array(positions.count * 4);
|
|
32
|
+
// A dominant fixture family per building, with smaller tenant-floor and room
|
|
33
|
+
// differences. Independent seeds keep colour from tracking occupancy/brightness.
|
|
34
|
+
// These are illustrated white-light mixes, not surveyed fixture temperatures.
|
|
35
|
+
const buildingTint = hash(`${buildingId}:fixture`);
|
|
36
|
+
const architecturalTint = 0.15 + 0.65 * hash(`${buildingId}:architectural-fixture`);
|
|
32
37
|
const samePoint = (a, b) => positions.getX(a) === positions.getX(b) && positions.getY(a) === positions.getY(b) && positions.getZ(a) === positions.getZ(b);
|
|
33
38
|
for (let first = 0; first < positions.count;) {
|
|
34
39
|
const name = materialOf(first), crown = architectural.has(name);
|
|
@@ -61,6 +66,9 @@ export function nightWindowMask(geometry, buildingId, materialOf) {
|
|
|
61
66
|
const floor = Math.round(y / count / 3.4), across = Math.floor(x / count / 7), depth = Math.floor(z / count / 7);
|
|
62
67
|
const room = hash(`${buildingId}:${floor}:${across}:${depth}`);
|
|
63
68
|
const brightness = hash(`${buildingId}:${floor}:${across}:${depth}:brightness`);
|
|
69
|
+
const tint = crown ? architecturalTint : 0.65 * buildingTint
|
|
70
|
+
+ 0.2 * hash(`${buildingId}:${floor}:fixture`)
|
|
71
|
+
+ 0.15 * hash(`${buildingId}:${floor}:${across}:${depth}:fixture`);
|
|
64
72
|
for (let vertex = first; vertex < end; vertex += 1) {
|
|
65
73
|
if (materialOf(vertex) !== name)
|
|
66
74
|
continue;
|
|
@@ -68,7 +76,7 @@ export function nightWindowMask(geometry, buildingId, materialOf) {
|
|
|
68
76
|
mask[vertex * 4] = rank;
|
|
69
77
|
mask[vertex * 4 + 1] = crown ? 0.3 : 0.25 + brightness * 0.45;
|
|
70
78
|
mask[vertex * 4 + 2] = crown ? 2 : 1;
|
|
71
|
-
mask[vertex * 4 + 3] =
|
|
79
|
+
mask[vertex * 4 + 3] = tint;
|
|
72
80
|
}
|
|
73
81
|
first = end;
|
|
74
82
|
}
|
|
@@ -2,8 +2,9 @@ import * as THREE from "../vendor/three-r186.js";
|
|
|
2
2
|
import { createBuilder, polygonOf } from "./building-kit.js";
|
|
3
3
|
import { chainSkin, chainsOf, orient, paintedSlab, planOf, turnsAt } from "./facade-grid.js";
|
|
4
4
|
// The Sheraton Grand Chicago Riverwalk, 301 East North Water Street (Solomon Cordwell Buenz,
|
|
5
|
-
// 1992): the geographic layout's model, on
|
|
6
|
-
// 1269924307. The original layout has no
|
|
5
|
+
// 1992): the geographic layout's model, on mapped tower 592122464, corner part
|
|
6
|
+
// 1269924307, and east-wing parts 1269924305/1269924304. The original layout has no
|
|
7
|
+
// model of it. The drawing's `buckingham-east` group,
|
|
7
8
|
// right of The Buckingham, is its corner: a lit crown over a tower of windows.
|
|
8
9
|
//
|
|
9
10
|
// A cream precast tower on an L, its arms ending in round towers, of punched windows at the
|
|
@@ -17,17 +18,21 @@ export const sheratonLevels = Object.freeze({
|
|
|
17
18
|
topFloor: 97, // the Skyscraper Center's occupied height, the corner's top floor
|
|
18
19
|
corner: 100.1, // the corner's roof, a floor-to-roof of 3.1 m over it as Emporis gives it
|
|
19
20
|
arms: 100.1 - 2 * 2.67, // the arms' roof, two floors lower, at the mapped arms' 32 levels
|
|
21
|
+
wing: 100.1 - 6 * 2.67, // 28-level east wing, relative to the 34-level corner's datum
|
|
22
|
+
wingEnd: 100.1 - 5 * 2.67, // the mapped 29-level round end, a floor above its wing
|
|
20
23
|
drum: 12.2, // the drums' height, Emporis's architectural height over its main roof
|
|
21
24
|
top: 112.3, // the corner drum's top, the Skyscraper Center's architectural height
|
|
22
25
|
});
|
|
23
26
|
const h = sheratonLevels;
|
|
24
27
|
// The round ends the drums stand on, circles fitted to the mapped parts' arcs within 11 cm:
|
|
25
28
|
// the corner, part 1269924307, and the east and north arms' ends, parts 1269924306 and
|
|
26
|
-
// 1269924308
|
|
29
|
+
// 1269924308, plus the east wing's end, 1269924304 (within 1.7 cm).
|
|
30
|
+
// Centres are east and north metres from Crain's centre; map precision is not survey accuracy.
|
|
27
31
|
export const sheratonDrums = Object.freeze([
|
|
28
32
|
{ centre: [407.44, 465.95], radius: 7.97, base: h.corner },
|
|
29
33
|
{ centre: [439.52, 462.06], radius: 7.37, base: h.arms },
|
|
30
34
|
{ centre: [409.75, 498.32], radius: 7.39, base: h.arms },
|
|
35
|
+
{ centre: [482.703, 457.389], radius: 7.391, base: h.wingEnd },
|
|
31
36
|
]);
|
|
32
37
|
// The skins' colours: the window tones, the precast, and the drums' cream and maroon fins,
|
|
33
38
|
// which no window shares.
|
|
@@ -69,6 +74,8 @@ export function createSheratonGrandGeographicBuilding(record, projectPlan, offse
|
|
|
69
74
|
return [centre[0] + radius * Math.cos(angle), -centre[1] + radius * Math.sin(angle)];
|
|
70
75
|
}));
|
|
71
76
|
const cornerPlan = orient(polygonOf(projectPlan(record.parts[1].coordinates)));
|
|
77
|
+
const wing = orient(polygonOf(projectPlan(record.parts[2].coordinates)));
|
|
78
|
+
const wingEnd = orient(polygonOf(projectPlan(record.parts[3].coordinates)));
|
|
72
79
|
// A closed solid of walls and slabs on an outline, painted by its runs.
|
|
73
80
|
const solid = (target, outline, lo, hi, paint) => {
|
|
74
81
|
planOf(outline).forEach((run, k) => {
|
|
@@ -82,13 +89,21 @@ export function createSheratonGrandGeographicBuilding(record, projectPlan, offse
|
|
|
82
89
|
// over it.
|
|
83
90
|
solid(shell, tower, 0, h.arms, () => core);
|
|
84
91
|
solid(shell, cornerPlan, h.arms, h.corner, () => core);
|
|
92
|
+
solid(shell, wing, 0, h.wing, () => core);
|
|
93
|
+
solid(shell, wingEnd, h.wing, h.wingEnd, () => core);
|
|
85
94
|
// Each drum: cream, a maroon fin every other facet, on its round end.
|
|
86
95
|
for (const { centre, radius, base } of sheratonDrums)
|
|
87
96
|
solid(drums, circle(centre, radius), base, base + h.drum, (k) => (k % 2 ? fin : drumFace));
|
|
88
97
|
// The skins, one box to a chain of runs turning less than 30°, so the round ends carry one:
|
|
89
98
|
// a window 1.4 m wide in each 2.4 m bay, set out from the chain's middle, on every floor.
|
|
90
|
-
const
|
|
99
|
+
const towerRows = rowsFor(0.25, h.arms);
|
|
100
|
+
const skin = (outline, base, top, seed, sharedWith) => {
|
|
91
101
|
const plan = planOf(outline), heights = rowsFor(base, top);
|
|
102
|
+
const same = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]) < 1e-6;
|
|
103
|
+
const shared = plan.map((run) => sharedWith?.some((a, index) => {
|
|
104
|
+
const b = sharedWith[(index + 1) % sharedWith.length];
|
|
105
|
+
return same(run.at(0), b) && same(run.at(run.length), a);
|
|
106
|
+
}) ?? false);
|
|
92
107
|
chainsOf(plan, 30 * Math.PI / 180).forEach((chain, index) => {
|
|
93
108
|
const first = chain.runs[0], last = chain.runs.at(-1);
|
|
94
109
|
const s0 = turnsAt(plan, first)[0] > 0 ? 0.09 : 0.02, s1 = chain.length - (turnsAt(plan, last)[1] > 0 ? 0.09 : 0.02);
|
|
@@ -96,14 +111,36 @@ export function createSheratonGrandGeographicBuilding(record, projectPlan, offse
|
|
|
96
111
|
const centres = Array.from({ length: 2 * count + 1 }, (_, k) => chain.length / 2 + (k - count) * 2.4);
|
|
97
112
|
const cuts = centres.flatMap((c) => [c - 0.7, c + 0.7]).filter((s) => s > s0 + 0.01 && s < s1 - 0.01);
|
|
98
113
|
const columns = [s0, ...cuts, s1];
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
// The two mapped parts meet along the old east drum's arc. Below the
|
|
115
|
+
// wing roof it is an internal wall: retain closed cores, but do not put
|
|
116
|
+
// overlapping facade skins on that shared boundary. Keep bay positions
|
|
117
|
+
// tied to the original chain when trimming the visible segments.
|
|
118
|
+
for (let k0 = 0; k0 < chain.runs.length;) {
|
|
119
|
+
if (shared[chain.runs[k0]]) {
|
|
120
|
+
k0 += 1;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
let k1 = k0;
|
|
124
|
+
while (k1 + 1 < chain.runs.length && !shared[chain.runs[k1 + 1]])
|
|
125
|
+
k1 += 1;
|
|
126
|
+
const from = k0 === 0 ? s0 : chain.starts[k0] + 0.09;
|
|
127
|
+
const to = k1 === chain.runs.length - 1 ? s1 : chain.starts[k1] + plan[chain.runs[k1]].length - 0.09;
|
|
128
|
+
chainSkin(kit, wall, plan, chain, k0, k1, from, to, columns, heights, 0.02, 0.07, (bay, r) => {
|
|
129
|
+
const middle = (columns[bay - 1] + columns[bay]) / 2;
|
|
130
|
+
// Keep the original tower's floor seed through the split at the
|
|
131
|
+
// wing roof, rather than restarting its window pattern above it.
|
|
132
|
+
const row = seed === 0 ? towerRows.findIndex((y) => Math.abs(y - heights[r]) < 1e-5) : r;
|
|
133
|
+
return r % 2 === 1 && centres.some((c) => Math.abs(middle - c) < 0.7) ? paneColor(row, bay, seed * 16 + index) : precast;
|
|
134
|
+
}, precast);
|
|
135
|
+
k0 = k1 + 1;
|
|
136
|
+
}
|
|
103
137
|
});
|
|
104
138
|
};
|
|
105
|
-
skin(tower, 0.25, h.
|
|
139
|
+
skin(tower, 0.25, h.wing, 0, wing);
|
|
140
|
+
skin(tower, h.wing, h.arms, 0);
|
|
106
141
|
skin(cornerPlan, h.arms, h.corner, 1);
|
|
142
|
+
skin(wing, 0.25, h.wing, 2, tower);
|
|
143
|
+
skin(wingEnd, h.wing, h.wingEnd, 3);
|
|
107
144
|
const model = kit.finish({ height: h.top, outlines: [shell, drums], opacity: 0.16 });
|
|
108
145
|
model.building.position.set(offset[0], 0, offset[1]);
|
|
109
146
|
model.building.userData["geography"] = record;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// Ascher Buildings and the Chicago Athletic Association 2026-09-29, and those of Two Illinois
|
|
9
9
|
// Center and River Plaza, with River Plaza's parts, the Hyatt Regency West Tower's part, the
|
|
10
10
|
// Sheraton Grand's tower and corner parts, Three Illinois Center's outline, and the Swissôtel's
|
|
11
|
-
// tower part, the same day.
|
|
11
|
+
// tower part, the same day. Sheraton Grand's east-wing parts were added from
|
|
12
|
+
// the OSM API on 2026-10-17; their roof heights are estimates on the tower datum.
|
|
12
13
|
// © OpenStreetMap contributors, ODbL 1.0.
|
|
13
14
|
// Coordinates are [longitude, latitude] in WGS84. See docs/skyline-geography.md.
|
|
14
15
|
export const geographicBuildings = [
|
|
@@ -43,7 +44,7 @@ export const geographicBuildings = [
|
|
|
43
44
|
{ "id": "building-office-west-of-aon", "name": "Two Illinois Center", "shortName": "Two Illinois Center", "height": 114.3, "tipHeight": 114.3, "heightSource": "https://www.skyscrapercenter.com/chicago/two-illinois-center/11095", "note": "Detailed Two Illinois Center: the mapped outline; a dark curtain wall of 32 storeys, mullions on a 5 ft module with glass over a spandrel on every floor at the photograph's 3.55 m pitch, and the top two floors' mechanical band, to a flat roof at the Skyscraper Center's 114.3 m. From the photograph's camera its lit floors fill the lower part of the gap between Two Prudential Plaza and Aon Center, in front of River Plaza.", "footprint": { "way": 236770799, "version": 3, "coordinates": [[-87.6235661, 41.8871124], [-87.6235636, 41.8871174], [-87.6235524, 41.8871216], [-87.6225645, 41.8871339], [-87.622551, 41.8871289], [-87.6225466, 41.8871186], [-87.622541, 41.8867998], [-87.6225469, 41.8867912], [-87.6225543, 41.8867879], [-87.6225638, 41.8867865], [-87.6228061, 41.8867819], [-87.6231721, 41.8867773], [-87.6235523, 41.886775], [-87.623557, 41.8867781], [-87.6235613, 41.8867836]] }, "parts": [{ "way": 236770799, "version": 3, "coordinates": [[-87.6235661, 41.8871124], [-87.6235636, 41.8871174], [-87.6235524, 41.8871216], [-87.6225645, 41.8871339], [-87.622551, 41.8871289], [-87.6225466, 41.8871186], [-87.622541, 41.8867998], [-87.6225469, 41.8867912], [-87.6225543, 41.8867879], [-87.6225638, 41.8867865], [-87.6228061, 41.8867819], [-87.6231721, 41.8867773], [-87.6235523, 41.886775], [-87.623557, 41.8867781], [-87.6235613, 41.8867836]], "bottom": 0, "top": 114.3 }] },
|
|
44
45
|
{ "id": "building-river-plaza", "name": "River Plaza", "shortName": "River Plaza", "height": 159.7, "tipHeight": 159.7, "heightSource": "https://www.skyscrapercenter.com/chicago/river-plaza/3383", "note": "Detailed River Plaza: the mapped outline and its parts; a white concrete slab of 56 storeys with notched corners, a grid of exposed frame and punched windows over the podium on the river side, to a flat roof at OpenStreetMap's 156 m, under the box on it to the Skyscraper Center's 159.7 m, its architectural height and tip; OpenStreetMap's 166 m for the box stands over the published tip. From the photograph's camera its top fills the gap between Two Prudential Plaza and Aon Center behind Two Illinois Center, the only mapped building on that sightline tall enough to reach the drawn top.", "footprint": { "way": 285867424, "version": 6, "coordinates": [[-87.6260818, 41.8900013], [-87.6261095, 41.8899782], [-87.6261086, 41.8899259], [-87.6261285, 41.8899127], [-87.6261269, 41.8898753], [-87.6261044, 41.8898588], [-87.6261035, 41.8898229], [-87.626103, 41.8898052], [-87.6260771, 41.8897865], [-87.6259425, 41.8896905], [-87.6257902, 41.8895791], [-87.6257288, 41.8895898], [-87.6255231, 41.8896255], [-87.6255256, 41.8897934], [-87.6254949, 41.8897941], [-87.6254648, 41.889817], [-87.6254664, 41.8898714], [-87.6254461, 41.8898877], [-87.6254474, 41.8899257], [-87.6254684, 41.8899384], [-87.6254701, 41.8899906], [-87.6255035, 41.8900132], [-87.625675, 41.8900085], [-87.6256968, 41.8899927], [-87.6258816, 41.8899874], [-87.6259031, 41.8900027]] }, "parts": [{ "way": 68796725, "version": 10, "coordinates": [[-87.6254648, 41.889817], [-87.6254664, 41.8898714], [-87.6254461, 41.8898877], [-87.6254474, 41.8899257], [-87.6254684, 41.8899384], [-87.6254701, 41.8899906], [-87.6255035, 41.8900132], [-87.625675, 41.8900085], [-87.6256968, 41.8899927], [-87.6258816, 41.8899874], [-87.6259031, 41.8900027], [-87.6260818, 41.8900013], [-87.6261095, 41.8899782], [-87.6261086, 41.8899259], [-87.6261285, 41.8899127], [-87.6261269, 41.8898753], [-87.6261044, 41.8898588], [-87.6261035, 41.8898229], [-87.626103, 41.8898052], [-87.6260771, 41.8897865], [-87.6258989, 41.8897905], [-87.6258799, 41.8898052], [-87.6256906, 41.8898092], [-87.6256664, 41.8897938], [-87.6255256, 41.8897934], [-87.6254949, 41.8897941]], "bottom": 0, "top": 156 }, { "way": 68796733, "version": 5, "coordinates": [[-87.6260771, 41.8897865], [-87.6259425, 41.8896905], [-87.6257902, 41.8895791], [-87.6257288, 41.8895898], [-87.6255231, 41.8896255], [-87.6255256, 41.8897934], [-87.6256664, 41.8897938], [-87.6256906, 41.8898092], [-87.6258799, 41.8898052], [-87.6258989, 41.8897905]], "bottom": 0, "top": 8 }, { "way": 285867425, "version": 1, "coordinates": [[-87.6258543, 41.8899579], [-87.6257153, 41.8899611], [-87.6257111, 41.8898629], [-87.6258501, 41.8898597]], "bottom": 156, "top": 159.7 }] },
|
|
45
46
|
{ "id": "building-hyatt-regency-west-tower", "name": "Hyatt Regency Chicago West Tower", "shortName": "Hyatt West Tower", "height": 111.3, "tipHeight": 111.3, "heightSource": "https://www.skyscrapercenter.com/building/hyatt-regency-chicago-west-tower/13132", "note": "Detailed Hyatt Regency Chicago West Tower: its mapped part of the hotel's outline; a brick slab of 33 storeys, narrow window slots continuous from the lobby to the top two floors on a 3.5 m module between solid corners, and a plain band over them to a flat roof at the Skyscraper Center's 111.3 m. From the photograph's camera it stands between Aon Center and the Blue Cross and Blue Shield Tower, where the drawing's Blue Cross group paints a dark brown sliver.", "footprint": { "way": 235920252, "version": 4, "coordinates": [[-87.6227574, 41.887587], [-87.6228537, 41.887584], [-87.6228521, 41.8874603], [-87.6228518, 41.8874165], [-87.6225427, 41.8874231], [-87.6225408, 41.8875106], [-87.6225253, 41.8875109], [-87.6225266, 41.8875277], [-87.6225277, 41.8875513], [-87.622528, 41.8875746], [-87.6225382, 41.8878947], [-87.62255, 41.8878954], [-87.6225496, 41.8879846], [-87.6228658, 41.8879789], [-87.6228643, 41.8879388], [-87.6228611, 41.8878099], [-87.6227664, 41.8878103]] }, "parts": [{ "way": 235920252, "version": 4, "coordinates": [[-87.6227574, 41.887587], [-87.6228537, 41.887584], [-87.6228521, 41.8874603], [-87.6228518, 41.8874165], [-87.6225427, 41.8874231], [-87.6225408, 41.8875106], [-87.6225253, 41.8875109], [-87.6225266, 41.8875277], [-87.6225277, 41.8875513], [-87.622528, 41.8875746], [-87.6225382, 41.8878947], [-87.62255, 41.8878954], [-87.6225496, 41.8879846], [-87.6228658, 41.8879789], [-87.6228643, 41.8879388], [-87.6228611, 41.8878099], [-87.6227664, 41.8878103]], "bottom": 0, "top": 111.3 }] },
|
|
46
|
-
{ "id": "building-buckingham-east", "name": "Sheraton Grand Chicago Riverwalk", "shortName": "Sheraton Grand", "height": 112.3, "tipHeight": 112.3, "heightSource": "https://www.skyscrapercenter.com/building/sheraton-chicago-hotel-towers/10020", "note": "Detailed Sheraton Grand Chicago Riverwalk: the mapped tower and its corner part; a cream precast tower on an L of punched windows at Emporis's 2.67 m floor-to-floor, its arms to 94.76 m, the corner's round tower two floors higher to 100.1 m, and a drum of cream and maroon fins on each round end, the corner's to the Skyscraper Center's 112.3 m. Heights stand on the Skyscraper Center's datum: its top floor at 97 m, a floor-to-roof of 3.1 m and 12.2 m drums as Emporis gives them; the published height nearest the drawing's reading, over Emporis's 330.56 ft. The Skyscraper Center's 112.8 m tip, 50 cm over the drum, is not modelled. The drawing's buckingham-east group is its corner's lit drum over its tower.", "footprint": { "way": 592122464, "version": 5, "coordinates": [[-87.6199499, 41.8891119], [-87.6199176, 41.8893381], [-87.6199306, 41.8893589], [-87.6199502, 41.8893716], [-87.6199845, 41.8893878], [-87.6200276, 41.8893957], [-87.6200732, 41.8893915], [-87.6201147, 41.8893744], [-87.6201372, 41.8893482], [-87.6201463, 41.8893162], [-87.6201584, 41.8892447], [-87.6201841, 41.889063], [-87.6201875, 41.8890302], [-87.6201812, 41.8889946], [-87.6201588, 41.8889641], [-87.620121, 41.8889384], [-87.6200743, 41.8889276], [-87.6196505, 41.8888958], [-87.6196025, 41.8889162], [-87.6195726, 41.8889427], [-87.6195591, 41.8889735], [-87.6195627, 41.8890068], [-87.6195846, 41.8890373], [-87.6196228, 41.8890638], [-87.6198263, 41.8890777], [-87.6198225, 41.889104]] }, "parts": [{ "way": 592122464, "version": 5, "coordinates": [[-87.6199499, 41.8891119], [-87.6199176, 41.8893381], [-87.6199306, 41.8893589], [-87.6199502, 41.8893716], [-87.6199845, 41.8893878], [-87.6200276, 41.8893957], [-87.6200732, 41.8893915], [-87.6201147, 41.8893744], [-87.6201372, 41.8893482], [-87.6201463, 41.8893162], [-87.6201584, 41.8892447], [-87.6201841, 41.889063], [-87.6201875, 41.8890302], [-87.6201812, 41.8889946], [-87.6201588, 41.8889641], [-87.620121, 41.8889384], [-87.6200743, 41.8889276], [-87.6196505, 41.8888958], [-87.6196025, 41.8889162], [-87.6195726, 41.8889427], [-87.6195591, 41.8889735], [-87.6195627, 41.8890068], [-87.6195846, 41.8890373], [-87.6196228, 41.8890638], [-87.6198263, 41.8890777], [-87.6198225, 41.889104]], "bottom": 0, "top": 94.76 }, { "way": 1269924307, "version": 1, "coordinates": [[-87.6199691, 41.8890378], [-87.6199677, 41.889013], [-87.6199719, 41.8889952], [-87.6199903, 41.8889744], [-87.6200135, 41.88896], [-87.6200416, 41.8889517], [-87.6200717, 41.8889503], [-87.6201037, 41.8889568], [-87.6201308, 41.8889711], [-87.6201498, 41.8889914], [-87.6201583, 41.8890152], [-87.6201555, 41.8890398], [-87.6201415, 41.8890622], [-87.6201241, 41.8890776], [-87.6200882, 41.8890904], [-87.6200552, 41.8890928], [-87.6200258, 41.8890875], [-87.6200001, 41.8890757], [-87.6199805, 41.8890586]], "bottom": 94.76, "top": 100.1 }] },
|
|
47
|
+
{ "id": "building-buckingham-east", "name": "Sheraton Grand Chicago Riverwalk", "shortName": "Sheraton Grand", "height": 112.3, "tipHeight": 112.3, "heightSource": "https://www.skyscrapercenter.com/building/sheraton-chicago-hotel-towers/10020", "note": "Detailed Sheraton Grand Chicago Riverwalk: the mapped tower and its corner part; a cream precast tower on an L of punched windows at Emporis's 2.67 m floor-to-floor, its arms to 94.76 m, the corner's round tower two floors higher to 100.1 m, and a drum of cream and maroon fins on each round end, the corner's to the Skyscraper Center's 112.3 m. Heights stand on the Skyscraper Center's datum: its top floor at 97 m, a floor-to-roof of 3.1 m and 12.2 m drums as Emporis gives them; the published height nearest the drawing's reading, over Emporis's 330.56 ft. The Skyscraper Center's 112.8 m tip, 50 cm over the drum, is not modelled. The drawing's buckingham-east group is its corner's lit drum over its tower. The mapped east wing and its round end continue the hotel to the east, with estimated roofs at 84.08 m and 86.75 m on the same height datum and a fourth crown. Photographs from before 2013 establish the wing; exact roof and crown dimensions remain inferred.", "footprint": { "way": 592122464, "version": 5, "coordinates": [[-87.6199499, 41.8891119], [-87.6199176, 41.8893381], [-87.6199306, 41.8893589], [-87.6199502, 41.8893716], [-87.6199845, 41.8893878], [-87.6200276, 41.8893957], [-87.6200732, 41.8893915], [-87.6201147, 41.8893744], [-87.6201372, 41.8893482], [-87.6201463, 41.8893162], [-87.6201584, 41.8892447], [-87.6201841, 41.889063], [-87.6201875, 41.8890302], [-87.6201812, 41.8889946], [-87.6201588, 41.8889641], [-87.620121, 41.8889384], [-87.6200743, 41.8889276], [-87.6196505, 41.8888958], [-87.6196025, 41.8889162], [-87.6195726, 41.8889427], [-87.6195591, 41.8889735], [-87.6195627, 41.8890068], [-87.6195846, 41.8890373], [-87.6196228, 41.8890638], [-87.6198263, 41.8890777], [-87.6198225, 41.889104]] }, "parts": [{ "way": 592122464, "version": 5, "coordinates": [[-87.6199499, 41.8891119], [-87.6199176, 41.8893381], [-87.6199306, 41.8893589], [-87.6199502, 41.8893716], [-87.6199845, 41.8893878], [-87.6200276, 41.8893957], [-87.6200732, 41.8893915], [-87.6201147, 41.8893744], [-87.6201372, 41.8893482], [-87.6201463, 41.8893162], [-87.6201584, 41.8892447], [-87.6201841, 41.889063], [-87.6201875, 41.8890302], [-87.6201812, 41.8889946], [-87.6201588, 41.8889641], [-87.620121, 41.8889384], [-87.6200743, 41.8889276], [-87.6196505, 41.8888958], [-87.6196025, 41.8889162], [-87.6195726, 41.8889427], [-87.6195591, 41.8889735], [-87.6195627, 41.8890068], [-87.6195846, 41.8890373], [-87.6196228, 41.8890638], [-87.6198263, 41.8890777], [-87.6198225, 41.889104]], "bottom": 0, "top": 94.76 }, { "way": 1269924307, "version": 1, "coordinates": [[-87.6199691, 41.8890378], [-87.6199677, 41.889013], [-87.6199719, 41.8889952], [-87.6199903, 41.8889744], [-87.6200135, 41.88896], [-87.6200416, 41.8889517], [-87.6200717, 41.8889503], [-87.6201037, 41.8889568], [-87.6201308, 41.8889711], [-87.6201498, 41.8889914], [-87.6201583, 41.8890152], [-87.6201555, 41.8890398], [-87.6201415, 41.8890622], [-87.6201241, 41.8890776], [-87.6200882, 41.8890904], [-87.6200552, 41.8890928], [-87.6200258, 41.8890875], [-87.6200001, 41.8890757], [-87.6199805, 41.8890586]], "bottom": 94.76, "top": 100.1 }, { "way": 1269924305, "version": 1, "coordinates": [[-87.6191033, 41.8890226], [-87.6190622, 41.8889954], [-87.6190428, 41.8889627], [-87.6190431, 41.8889236], [-87.6190607, 41.8888928], [-87.619091, 41.8888724], [-87.6191278, 41.8888566], [-87.6193634, 41.8888743], [-87.6196505, 41.8888958], [-87.6196025, 41.8889162], [-87.6195726, 41.8889427], [-87.6195591, 41.8889735], [-87.6195627, 41.8890068], [-87.6195846, 41.8890373], [-87.6196228, 41.8890638]], "bottom": 0, "top": 84.08, "estimatedHeight": true }, { "way": 1269924304, "version": 1, "coordinates": [[-87.619112, 41.8888866], [-87.61909, 41.8888996], [-87.6190746, 41.888917], [-87.6190672, 41.8889372], [-87.6190685, 41.8889581], [-87.6190786, 41.8889777], [-87.6190963, 41.8889939], [-87.6191199, 41.8890052], [-87.6191471, 41.8890105], [-87.6191778, 41.8890088], [-87.6192059, 41.8889994], [-87.619228, 41.8889835], [-87.6192416, 41.8889629], [-87.6192449, 41.8889401], [-87.6192376, 41.8889179], [-87.6192207, 41.8888988], [-87.6191959, 41.8888851], [-87.6191665, 41.8888785], [-87.6191384, 41.8888793]], "bottom": 84.08, "top": 86.75, "estimatedHeight": true }], "groundFootprintWays": [592122464, 1269924305] },
|
|
47
48
|
{ "id": "building-three-illinois-center", "name": "Three Illinois Center", "shortName": "Three Illinois Center", "height": 106.7, "tipHeight": 106.7, "heightSource": "https://www.skyscrapercenter.com/building/three-illinois-center/13913", "note": "Detailed Three Illinois Center: the mapped outline; a dark bronze curtain wall of 30 storeys, mullions on a 5 ft module and glass over a spandrel on each floor at the published 11.5 ft slab to slab over a 5.06 m lobby, and the top two floors' windowless mechanical penthouse, glazed about 2019 but kept as the 2013 photograph shows it, under a flat roof at the Skyscraper Center's and Emporis's 350 ft. Its east end fills the gap between 340 on the Park and The Buckingham, where the drawing's buckingham-west group paints an office tower whose top stands about 140 layer units higher.", "footprint": { "way": 95486958, "version": 12, "coordinates": [[-87.6204966, 41.8869542], [-87.6202534, 41.8869566], [-87.6200384, 41.8869571], [-87.6200396, 41.887005], [-87.6200455, 41.8873733], [-87.620058, 41.8876286], [-87.6205101, 41.8876189], [-87.6205054, 41.8873683]] }, "parts": [{ "way": 95486958, "version": 12, "coordinates": [[-87.6204966, 41.8869542], [-87.6202534, 41.8869566], [-87.6200384, 41.8869571], [-87.6200396, 41.887005], [-87.6200455, 41.8873733], [-87.620058, 41.8876286], [-87.6205101, 41.8876189], [-87.6205054, 41.8873683]], "bottom": 0, "top": 106.7 }] },
|
|
48
49
|
{ "id": "building-swissotel", "name": "Swissôtel Chicago", "shortName": "Swissôtel", "height": 139.3, "tipHeight": 139.3, "heightSource": "https://www.skyscrapercenter.com/building/swissotel-chicago/4362", "note": "Detailed Swissôtel Chicago: the hotel's mapped tower part, a triangle; a flush grid of reflective glass, thin mullions about 1.5 m apart and a transom at each of OpenStreetMap's 45 levels, under a flat roof at the Skyscraper Center's 139.3 m. It stands behind Three Illinois Center and The Buckingham from the drawing's camera, and fills the seam where their corners meet. The hotel's outline, 95486966, adds a small block on the tower's north-east face, and its podium, 95486953, carries no height; neither is modelled.", "footprint": { "way": 641288601, "version": 3, "coordinates": [[-87.6194102, 41.8874254], [-87.619534, 41.8875706], [-87.619676, 41.8873866], [-87.6198184, 41.8871906], [-87.6192311, 41.8871971], [-87.619375, 41.8873843]] }, "parts": [{ "way": 641288601, "version": 3, "coordinates": [[-87.6194102, 41.8874254], [-87.619534, 41.8875706], [-87.619676, 41.8873866], [-87.6198184, 41.8871906], [-87.6192311, 41.8871971], [-87.619375, 41.8873843]], "bottom": 0, "top": 139.3 }] },
|
|
49
50
|
];
|
|
@@ -10,6 +10,13 @@ export declare function footprintMetrics(coordinates: [number, number][]): {
|
|
|
10
10
|
size: Vec2;
|
|
11
11
|
area: number;
|
|
12
12
|
};
|
|
13
|
+
export declare function groundFootprints(record: GeoBuilding): GeoBuilding["footprint"][];
|
|
14
|
+
export declare function buildingGroundBounds(record: GeoBuilding): {
|
|
15
|
+
min: Vec2;
|
|
16
|
+
max: Vec2;
|
|
17
|
+
center: Vec2;
|
|
18
|
+
size: Vec2;
|
|
19
|
+
};
|
|
13
20
|
export declare function createGeographicBuilding(record: GeoBuilding, offset?: [number, number]): BuildingModel;
|
|
14
21
|
export declare function streetWidth(street: GeoStreet): number;
|
|
15
22
|
export declare function createGeographicRoads(bounds: {
|
|
@@ -56,6 +56,23 @@ export function footprintMetrics(coordinates) {
|
|
|
56
56
|
}, 0)) / 2;
|
|
57
57
|
return { min, max, center: min.map((n, i) => (n + max[i]) / 2), size: min.map((n, i) => max[i] - n), area };
|
|
58
58
|
}
|
|
59
|
+
// Some complexes have separately mapped wings beyond the primary way. Keep
|
|
60
|
+
// their source ways intact, and opt in only disjoint ground parts of this model.
|
|
61
|
+
export function groundFootprints(record) {
|
|
62
|
+
if (!record.groundFootprintWays)
|
|
63
|
+
return [record.footprint];
|
|
64
|
+
return record.groundFootprintWays.map((way) => {
|
|
65
|
+
const part = record.parts.find((entry) => entry.way === way && entry.bottom === 0);
|
|
66
|
+
if (!part)
|
|
67
|
+
throw new Error(`Missing ground footprint ${way} for ${record.id}`);
|
|
68
|
+
return part;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export function buildingGroundBounds(record) {
|
|
72
|
+
// Only extrema are meaningful for concatenated rings, not a polygon's area.
|
|
73
|
+
const { min, max, center, size } = footprintMetrics(groundFootprints(record).flatMap((part) => part.coordinates));
|
|
74
|
+
return { min, max, center, size };
|
|
75
|
+
}
|
|
59
76
|
function plan(coordinates) {
|
|
60
77
|
const points = coordinates.map((p) => { const [east, north] = projectGround(p); return [east, -north]; });
|
|
61
78
|
const area = points.reduce((sum, a, i) => {
|
|
@@ -313,8 +330,20 @@ export function createGeographicGround(offset = [0, 0]) {
|
|
|
313
330
|
group.add(streets);
|
|
314
331
|
const outlines = [];
|
|
315
332
|
for (const record of geographicBuildings) {
|
|
316
|
-
const
|
|
317
|
-
|
|
333
|
+
const edges = new Map();
|
|
334
|
+
for (const footprint of groundFootprints(record)) {
|
|
335
|
+
const points = footprint.coordinates.map((p) => { const [x, y] = projectGround(p); return [x, 0.1, -y]; });
|
|
336
|
+
points.forEach((p, i) => {
|
|
337
|
+
const q = points[(i + 1) % points.length];
|
|
338
|
+
const key = [p.join(","), q.join(",")].sort().join(";");
|
|
339
|
+
const prior = edges.get(key);
|
|
340
|
+
edges.set(key, { points: [p, q], count: (prior?.count ?? 0) + 1 });
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
// Shared mapped boundaries are inside the complex, not its ground outline.
|
|
344
|
+
for (const edge of edges.values())
|
|
345
|
+
if (edge.count === 1)
|
|
346
|
+
outlines.push(...edge.points);
|
|
318
347
|
}
|
|
319
348
|
const footprints = lines(outlines, 0xc9c9c9);
|
|
320
349
|
group.add(footprints);
|
package/package.json
CHANGED
package/site/building-detail.js
CHANGED
|
@@ -2,7 +2,7 @@ import * as THREE from "./vendor/three-r186.js";
|
|
|
2
2
|
import { createBuildingStudy } from "./study-viewer.js";
|
|
3
3
|
import { skylineAzimuth } from "./skyline-comparison.js";
|
|
4
4
|
import { geographicBuildings } from "./models/skyline-geography-data.js";
|
|
5
|
-
import { createGeographicBuilding,
|
|
5
|
+
import { createGeographicBuilding, buildingGroundBounds, groundFootprints, geographicOrigin } from "./models/skyline-geography.js";
|
|
6
6
|
import { createTimeLighting } from "./time-of-day.js";
|
|
7
7
|
import { createDaylight, createSkylineColour } from "./models/colour-materials.js";
|
|
8
8
|
// One mapped building of the 3D skyline, alone on its own platform, as the building study
|
|
@@ -19,7 +19,7 @@ export function showBuildingDetail({ root, id, frame, signal, colour = false, on
|
|
|
19
19
|
return undefined;
|
|
20
20
|
}
|
|
21
21
|
function showBuilding(record, root, frame, signal, initiallyColoured, onColourChange) {
|
|
22
|
-
const footprint =
|
|
22
|
+
const footprint = buildingGroundBounds(record);
|
|
23
23
|
// Centred on its mapped outline: ground coordinates are east and north, the scene's x and -z.
|
|
24
24
|
const model = createGeographicBuilding(record, [-footprint.center[0], footprint.center[1]]);
|
|
25
25
|
// The page names the building before the 3D view starts, so a failed start leaves it named.
|
|
@@ -35,8 +35,11 @@ function showBuilding(record, root, frame, signal, initiallyColoured, onColourCh
|
|
|
35
35
|
const source = link(document, record.heightSource, record.heightFromDrawing ? `${number(record.height)}, measured on the drawing` : `${number(record.height)} architectural${osm}`);
|
|
36
36
|
heights.replaceChildren(source, ...record.tipHeight !== record.height ? [` · tip ${number(record.tipHeight)}`] : []);
|
|
37
37
|
root.querySelector("#building-note").textContent = record.note;
|
|
38
|
-
const
|
|
39
|
-
|
|
38
|
+
const ways = groundFootprints(record).flatMap((part, index) => [
|
|
39
|
+
...index ? [" and "] : [],
|
|
40
|
+
link(document, `https://www.openstreetmap.org/way/${part.way}`, `OpenStreetMap way ${part.way}`),
|
|
41
|
+
]);
|
|
42
|
+
root.querySelector("#building-facts").replaceChildren(`Mapped outline ${footprint.size[0].toFixed(1)} × ${footprint.size[1].toFixed(1)} m, from `, ...ways, `. Model: ${model.triangleCount.toLocaleString("en-US")} triangles.`);
|
|
40
43
|
const min = new THREE.Vector3(Infinity, Infinity, Infinity), max = new THREE.Vector3(-Infinity, -Infinity, -Infinity);
|
|
41
44
|
model.building.updateMatrixWorld(true);
|
|
42
45
|
model.building.traverse((child) => {
|
|
@@ -290,8 +290,9 @@ export const measuredColours = {
|
|
|
290
290
|
// above the horizon, each the median of a clear box of the same panorama (scripts/sample-colours.ts,
|
|
291
291
|
// the day study's sky), and the sun's warm white, chosen.
|
|
292
292
|
export const daylightColours = { sky: 0x9ebbdb, horizon: 0xc4d1e1, sun: 0xfff4e6 };
|
|
293
|
-
//
|
|
294
|
-
|
|
293
|
+
// Illustrated warm, neutral and cool white fixtures; not calibrated Kelvin values.
|
|
294
|
+
// DOE's white-tuning overview informs the range; see docs/viewer.md (time of day).
|
|
295
|
+
export const nightColours = { ambient: 0xa1afc8, fill: 0x8d9fbd, window: 0xffc17d, windowWhite: 0xffedda, windowCool: 0xdceaff };
|
|
295
296
|
// Illustrated clear-sky time previews. Solar elevation chooses the stops; these are
|
|
296
297
|
// artistic colours, separate from the photograph-calibrated daytime palette above.
|
|
297
298
|
export const timeOfDayColours = [
|
|
@@ -22,6 +22,7 @@ export function glassReflection(material, geometry, mask, windows = new Float32A
|
|
|
22
22
|
skylineWindowsSuppressed: { value: 0 },
|
|
23
23
|
skylineWindowWarm: { value: new THREE.Color(nightColours.window) },
|
|
24
24
|
skylineWindowWhite: { value: new THREE.Color(nightColours.windowWhite) },
|
|
25
|
+
skylineWindowCool: { value: new THREE.Color(nightColours.windowCool) },
|
|
25
26
|
};
|
|
26
27
|
const compile = (shader, renderer) => {
|
|
27
28
|
originalCompile.call(material, shader, renderer);
|
|
@@ -44,7 +45,8 @@ uniform float skylineDarkness;
|
|
|
44
45
|
uniform float skylineActivity;
|
|
45
46
|
uniform float skylineWindowsSuppressed;
|
|
46
47
|
uniform vec3 skylineWindowWarm;
|
|
47
|
-
uniform vec3 skylineWindowWhite
|
|
48
|
+
uniform vec3 skylineWindowWhite;
|
|
49
|
+
uniform vec3 skylineWindowCool;`).replace("#include <opaque_fragment>", `
|
|
48
50
|
if (vSkylineGlass > 0.0 && skylineReflecting > 0.0) {
|
|
49
51
|
vec3 eye = vec3(0.0);
|
|
50
52
|
eye.z = 1.0;
|
|
@@ -59,7 +61,10 @@ if (vSkylineWindow.z > 0.0 && skylineDarkness > 0.0) {
|
|
|
59
61
|
float occupied = 1.0 - smoothstep(skylineActivity - 0.05, skylineActivity + 0.05, vSkylineWindow.x);
|
|
60
62
|
float dusk = smoothstep(0.12 + 0.35 * vSkylineWindow.x, 0.45 + 0.35 * vSkylineWindow.x, skylineDarkness);
|
|
61
63
|
float light = vSkylineWindow.z > 1.5 ? skylineDarkness : occupied * dusk * (1.0 - skylineWindowsSuppressed);
|
|
62
|
-
|
|
64
|
+
float tint = vSkylineWindow.w;
|
|
65
|
+
vec3 lamp = tint < 0.5
|
|
66
|
+
? mix(skylineWindowWarm, skylineWindowWhite, tint * 2.0)
|
|
67
|
+
: mix(skylineWindowWhite, skylineWindowCool, (tint - 0.5) * 2.0);
|
|
63
68
|
outgoingLight += lamp * vSkylineWindow.y * light;
|
|
64
69
|
}
|
|
65
70
|
#include <opaque_fragment>`);
|
|
@@ -74,7 +79,7 @@ if (vSkylineWindow.z > 0.0 && skylineDarkness > 0.0) {
|
|
|
74
79
|
geometry.setAttribute("skylineGlass", attribute);
|
|
75
80
|
geometry.setAttribute("skylineWindow", windowAttribute);
|
|
76
81
|
material.onBeforeCompile = compile;
|
|
77
|
-
material.customProgramCacheKey = () => `${key}:skyline-facade-
|
|
82
|
+
material.customProgramCacheKey = () => `${key}:skyline-facade-v3`;
|
|
78
83
|
}
|
|
79
84
|
else {
|
|
80
85
|
material.onBeforeCompile = originalCompile;
|
|
@@ -99,7 +104,7 @@ if (vSkylineWindow.z > 0.0 && skylineDarkness > 0.0) {
|
|
|
99
104
|
uniforms.skylineDarkness.value = lighting?.darkness ?? 0;
|
|
100
105
|
uniforms.skylineWindowsSuppressed.value = celebration ? 1 : 0;
|
|
101
106
|
uniforms.skylineActivity.value = lighting?.activity ?? 0;
|
|
102
|
-
for (const [uniform, hex] of [[uniforms.skylineWindowWarm, nightColours.window], [uniforms.skylineWindowWhite, nightColours.windowWhite]]) {
|
|
107
|
+
for (const [uniform, hex] of [[uniforms.skylineWindowWarm, nightColours.window], [uniforms.skylineWindowWhite, nightColours.windowWhite], [uniforms.skylineWindowCool, nightColours.windowCool]]) {
|
|
103
108
|
uniform.value.setHex(hex);
|
|
104
109
|
if (!coloured) {
|
|
105
110
|
const { r, g, b } = uniform.value, grey = r * 0.2126 + g * 0.7152 + b * 0.0722;
|
|
@@ -29,6 +29,11 @@ const architectural = new Set(["lamp", "crown lights", "band lights", "sign lett
|
|
|
29
29
|
export function nightWindowMask(geometry, buildingId, materialOf) {
|
|
30
30
|
const positions = geometry.getAttribute("position"), normals = geometry.getAttribute("normal");
|
|
31
31
|
const mask = new Float32Array(positions.count * 4);
|
|
32
|
+
// A dominant fixture family per building, with smaller tenant-floor and room
|
|
33
|
+
// differences. Independent seeds keep colour from tracking occupancy/brightness.
|
|
34
|
+
// These are illustrated white-light mixes, not surveyed fixture temperatures.
|
|
35
|
+
const buildingTint = hash(`${buildingId}:fixture`);
|
|
36
|
+
const architecturalTint = 0.15 + 0.65 * hash(`${buildingId}:architectural-fixture`);
|
|
32
37
|
const samePoint = (a, b) => positions.getX(a) === positions.getX(b) && positions.getY(a) === positions.getY(b) && positions.getZ(a) === positions.getZ(b);
|
|
33
38
|
for (let first = 0; first < positions.count;) {
|
|
34
39
|
const name = materialOf(first), crown = architectural.has(name);
|
|
@@ -61,6 +66,9 @@ export function nightWindowMask(geometry, buildingId, materialOf) {
|
|
|
61
66
|
const floor = Math.round(y / count / 3.4), across = Math.floor(x / count / 7), depth = Math.floor(z / count / 7);
|
|
62
67
|
const room = hash(`${buildingId}:${floor}:${across}:${depth}`);
|
|
63
68
|
const brightness = hash(`${buildingId}:${floor}:${across}:${depth}:brightness`);
|
|
69
|
+
const tint = crown ? architecturalTint : 0.65 * buildingTint
|
|
70
|
+
+ 0.2 * hash(`${buildingId}:${floor}:fixture`)
|
|
71
|
+
+ 0.15 * hash(`${buildingId}:${floor}:${across}:${depth}:fixture`);
|
|
64
72
|
for (let vertex = first; vertex < end; vertex += 1) {
|
|
65
73
|
if (materialOf(vertex) !== name)
|
|
66
74
|
continue;
|
|
@@ -68,7 +76,7 @@ export function nightWindowMask(geometry, buildingId, materialOf) {
|
|
|
68
76
|
mask[vertex * 4] = rank;
|
|
69
77
|
mask[vertex * 4 + 1] = crown ? 0.3 : 0.25 + brightness * 0.45;
|
|
70
78
|
mask[vertex * 4 + 2] = crown ? 2 : 1;
|
|
71
|
-
mask[vertex * 4 + 3] =
|
|
79
|
+
mask[vertex * 4 + 3] = tint;
|
|
72
80
|
}
|
|
73
81
|
first = end;
|
|
74
82
|
}
|
|
@@ -2,8 +2,9 @@ import * as THREE from "../vendor/three-r186.js";
|
|
|
2
2
|
import { createBuilder, polygonOf } from "./building-kit.js";
|
|
3
3
|
import { chainSkin, chainsOf, orient, paintedSlab, planOf, turnsAt } from "./facade-grid.js";
|
|
4
4
|
// The Sheraton Grand Chicago Riverwalk, 301 East North Water Street (Solomon Cordwell Buenz,
|
|
5
|
-
// 1992): the geographic layout's model, on
|
|
6
|
-
// 1269924307. The original layout has no
|
|
5
|
+
// 1992): the geographic layout's model, on mapped tower 592122464, corner part
|
|
6
|
+
// 1269924307, and east-wing parts 1269924305/1269924304. The original layout has no
|
|
7
|
+
// model of it. The drawing's `buckingham-east` group,
|
|
7
8
|
// right of The Buckingham, is its corner: a lit crown over a tower of windows.
|
|
8
9
|
//
|
|
9
10
|
// A cream precast tower on an L, its arms ending in round towers, of punched windows at the
|
|
@@ -17,17 +18,21 @@ export const sheratonLevels = Object.freeze({
|
|
|
17
18
|
topFloor: 97, // the Skyscraper Center's occupied height, the corner's top floor
|
|
18
19
|
corner: 100.1, // the corner's roof, a floor-to-roof of 3.1 m over it as Emporis gives it
|
|
19
20
|
arms: 100.1 - 2 * 2.67, // the arms' roof, two floors lower, at the mapped arms' 32 levels
|
|
21
|
+
wing: 100.1 - 6 * 2.67, // 28-level east wing, relative to the 34-level corner's datum
|
|
22
|
+
wingEnd: 100.1 - 5 * 2.67, // the mapped 29-level round end, a floor above its wing
|
|
20
23
|
drum: 12.2, // the drums' height, Emporis's architectural height over its main roof
|
|
21
24
|
top: 112.3, // the corner drum's top, the Skyscraper Center's architectural height
|
|
22
25
|
});
|
|
23
26
|
const h = sheratonLevels;
|
|
24
27
|
// The round ends the drums stand on, circles fitted to the mapped parts' arcs within 11 cm:
|
|
25
28
|
// the corner, part 1269924307, and the east and north arms' ends, parts 1269924306 and
|
|
26
|
-
// 1269924308
|
|
29
|
+
// 1269924308, plus the east wing's end, 1269924304 (within 1.7 cm).
|
|
30
|
+
// Centres are east and north metres from Crain's centre; map precision is not survey accuracy.
|
|
27
31
|
export const sheratonDrums = Object.freeze([
|
|
28
32
|
{ centre: [407.44, 465.95], radius: 7.97, base: h.corner },
|
|
29
33
|
{ centre: [439.52, 462.06], radius: 7.37, base: h.arms },
|
|
30
34
|
{ centre: [409.75, 498.32], radius: 7.39, base: h.arms },
|
|
35
|
+
{ centre: [482.703, 457.389], radius: 7.391, base: h.wingEnd },
|
|
31
36
|
]);
|
|
32
37
|
// The skins' colours: the window tones, the precast, and the drums' cream and maroon fins,
|
|
33
38
|
// which no window shares.
|
|
@@ -69,6 +74,8 @@ export function createSheratonGrandGeographicBuilding(record, projectPlan, offse
|
|
|
69
74
|
return [centre[0] + radius * Math.cos(angle), -centre[1] + radius * Math.sin(angle)];
|
|
70
75
|
}));
|
|
71
76
|
const cornerPlan = orient(polygonOf(projectPlan(record.parts[1].coordinates)));
|
|
77
|
+
const wing = orient(polygonOf(projectPlan(record.parts[2].coordinates)));
|
|
78
|
+
const wingEnd = orient(polygonOf(projectPlan(record.parts[3].coordinates)));
|
|
72
79
|
// A closed solid of walls and slabs on an outline, painted by its runs.
|
|
73
80
|
const solid = (target, outline, lo, hi, paint) => {
|
|
74
81
|
planOf(outline).forEach((run, k) => {
|
|
@@ -82,13 +89,21 @@ export function createSheratonGrandGeographicBuilding(record, projectPlan, offse
|
|
|
82
89
|
// over it.
|
|
83
90
|
solid(shell, tower, 0, h.arms, () => core);
|
|
84
91
|
solid(shell, cornerPlan, h.arms, h.corner, () => core);
|
|
92
|
+
solid(shell, wing, 0, h.wing, () => core);
|
|
93
|
+
solid(shell, wingEnd, h.wing, h.wingEnd, () => core);
|
|
85
94
|
// Each drum: cream, a maroon fin every other facet, on its round end.
|
|
86
95
|
for (const { centre, radius, base } of sheratonDrums)
|
|
87
96
|
solid(drums, circle(centre, radius), base, base + h.drum, (k) => (k % 2 ? fin : drumFace));
|
|
88
97
|
// The skins, one box to a chain of runs turning less than 30°, so the round ends carry one:
|
|
89
98
|
// a window 1.4 m wide in each 2.4 m bay, set out from the chain's middle, on every floor.
|
|
90
|
-
const
|
|
99
|
+
const towerRows = rowsFor(0.25, h.arms);
|
|
100
|
+
const skin = (outline, base, top, seed, sharedWith) => {
|
|
91
101
|
const plan = planOf(outline), heights = rowsFor(base, top);
|
|
102
|
+
const same = (a, b) => Math.hypot(a[0] - b[0], a[1] - b[1]) < 1e-6;
|
|
103
|
+
const shared = plan.map((run) => sharedWith?.some((a, index) => {
|
|
104
|
+
const b = sharedWith[(index + 1) % sharedWith.length];
|
|
105
|
+
return same(run.at(0), b) && same(run.at(run.length), a);
|
|
106
|
+
}) ?? false);
|
|
92
107
|
chainsOf(plan, 30 * Math.PI / 180).forEach((chain, index) => {
|
|
93
108
|
const first = chain.runs[0], last = chain.runs.at(-1);
|
|
94
109
|
const s0 = turnsAt(plan, first)[0] > 0 ? 0.09 : 0.02, s1 = chain.length - (turnsAt(plan, last)[1] > 0 ? 0.09 : 0.02);
|
|
@@ -96,14 +111,36 @@ export function createSheratonGrandGeographicBuilding(record, projectPlan, offse
|
|
|
96
111
|
const centres = Array.from({ length: 2 * count + 1 }, (_, k) => chain.length / 2 + (k - count) * 2.4);
|
|
97
112
|
const cuts = centres.flatMap((c) => [c - 0.7, c + 0.7]).filter((s) => s > s0 + 0.01 && s < s1 - 0.01);
|
|
98
113
|
const columns = [s0, ...cuts, s1];
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
114
|
+
// The two mapped parts meet along the old east drum's arc. Below the
|
|
115
|
+
// wing roof it is an internal wall: retain closed cores, but do not put
|
|
116
|
+
// overlapping facade skins on that shared boundary. Keep bay positions
|
|
117
|
+
// tied to the original chain when trimming the visible segments.
|
|
118
|
+
for (let k0 = 0; k0 < chain.runs.length;) {
|
|
119
|
+
if (shared[chain.runs[k0]]) {
|
|
120
|
+
k0 += 1;
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
let k1 = k0;
|
|
124
|
+
while (k1 + 1 < chain.runs.length && !shared[chain.runs[k1 + 1]])
|
|
125
|
+
k1 += 1;
|
|
126
|
+
const from = k0 === 0 ? s0 : chain.starts[k0] + 0.09;
|
|
127
|
+
const to = k1 === chain.runs.length - 1 ? s1 : chain.starts[k1] + plan[chain.runs[k1]].length - 0.09;
|
|
128
|
+
chainSkin(kit, wall, plan, chain, k0, k1, from, to, columns, heights, 0.02, 0.07, (bay, r) => {
|
|
129
|
+
const middle = (columns[bay - 1] + columns[bay]) / 2;
|
|
130
|
+
// Keep the original tower's floor seed through the split at the
|
|
131
|
+
// wing roof, rather than restarting its window pattern above it.
|
|
132
|
+
const row = seed === 0 ? towerRows.findIndex((y) => Math.abs(y - heights[r]) < 1e-5) : r;
|
|
133
|
+
return r % 2 === 1 && centres.some((c) => Math.abs(middle - c) < 0.7) ? paneColor(row, bay, seed * 16 + index) : precast;
|
|
134
|
+
}, precast);
|
|
135
|
+
k0 = k1 + 1;
|
|
136
|
+
}
|
|
103
137
|
});
|
|
104
138
|
};
|
|
105
|
-
skin(tower, 0.25, h.
|
|
139
|
+
skin(tower, 0.25, h.wing, 0, wing);
|
|
140
|
+
skin(tower, h.wing, h.arms, 0);
|
|
106
141
|
skin(cornerPlan, h.arms, h.corner, 1);
|
|
142
|
+
skin(wing, 0.25, h.wing, 2, tower);
|
|
143
|
+
skin(wingEnd, h.wing, h.wingEnd, 3);
|
|
107
144
|
const model = kit.finish({ height: h.top, outlines: [shell, drums], opacity: 0.16 });
|
|
108
145
|
model.building.position.set(offset[0], 0, offset[1]);
|
|
109
146
|
model.building.userData["geography"] = record;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// Ascher Buildings and the Chicago Athletic Association 2026-09-29, and those of Two Illinois
|
|
9
9
|
// Center and River Plaza, with River Plaza's parts, the Hyatt Regency West Tower's part, the
|
|
10
10
|
// Sheraton Grand's tower and corner parts, Three Illinois Center's outline, and the Swissôtel's
|
|
11
|
-
// tower part, the same day.
|
|
11
|
+
// tower part, the same day. Sheraton Grand's east-wing parts were added from
|
|
12
|
+
// the OSM API on 2026-10-17; their roof heights are estimates on the tower datum.
|
|
12
13
|
// © OpenStreetMap contributors, ODbL 1.0.
|
|
13
14
|
// Coordinates are [longitude, latitude] in WGS84. See docs/skyline-geography.md.
|
|
14
15
|
export const geographicBuildings = [
|
|
@@ -43,7 +44,7 @@ export const geographicBuildings = [
|
|
|
43
44
|
{ "id": "building-office-west-of-aon", "name": "Two Illinois Center", "shortName": "Two Illinois Center", "height": 114.3, "tipHeight": 114.3, "heightSource": "https://www.skyscrapercenter.com/chicago/two-illinois-center/11095", "note": "Detailed Two Illinois Center: the mapped outline; a dark curtain wall of 32 storeys, mullions on a 5 ft module with glass over a spandrel on every floor at the photograph's 3.55 m pitch, and the top two floors' mechanical band, to a flat roof at the Skyscraper Center's 114.3 m. From the photograph's camera its lit floors fill the lower part of the gap between Two Prudential Plaza and Aon Center, in front of River Plaza.", "footprint": { "way": 236770799, "version": 3, "coordinates": [[-87.6235661, 41.8871124], [-87.6235636, 41.8871174], [-87.6235524, 41.8871216], [-87.6225645, 41.8871339], [-87.622551, 41.8871289], [-87.6225466, 41.8871186], [-87.622541, 41.8867998], [-87.6225469, 41.8867912], [-87.6225543, 41.8867879], [-87.6225638, 41.8867865], [-87.6228061, 41.8867819], [-87.6231721, 41.8867773], [-87.6235523, 41.886775], [-87.623557, 41.8867781], [-87.6235613, 41.8867836]] }, "parts": [{ "way": 236770799, "version": 3, "coordinates": [[-87.6235661, 41.8871124], [-87.6235636, 41.8871174], [-87.6235524, 41.8871216], [-87.6225645, 41.8871339], [-87.622551, 41.8871289], [-87.6225466, 41.8871186], [-87.622541, 41.8867998], [-87.6225469, 41.8867912], [-87.6225543, 41.8867879], [-87.6225638, 41.8867865], [-87.6228061, 41.8867819], [-87.6231721, 41.8867773], [-87.6235523, 41.886775], [-87.623557, 41.8867781], [-87.6235613, 41.8867836]], "bottom": 0, "top": 114.3 }] },
|
|
44
45
|
{ "id": "building-river-plaza", "name": "River Plaza", "shortName": "River Plaza", "height": 159.7, "tipHeight": 159.7, "heightSource": "https://www.skyscrapercenter.com/chicago/river-plaza/3383", "note": "Detailed River Plaza: the mapped outline and its parts; a white concrete slab of 56 storeys with notched corners, a grid of exposed frame and punched windows over the podium on the river side, to a flat roof at OpenStreetMap's 156 m, under the box on it to the Skyscraper Center's 159.7 m, its architectural height and tip; OpenStreetMap's 166 m for the box stands over the published tip. From the photograph's camera its top fills the gap between Two Prudential Plaza and Aon Center behind Two Illinois Center, the only mapped building on that sightline tall enough to reach the drawn top.", "footprint": { "way": 285867424, "version": 6, "coordinates": [[-87.6260818, 41.8900013], [-87.6261095, 41.8899782], [-87.6261086, 41.8899259], [-87.6261285, 41.8899127], [-87.6261269, 41.8898753], [-87.6261044, 41.8898588], [-87.6261035, 41.8898229], [-87.626103, 41.8898052], [-87.6260771, 41.8897865], [-87.6259425, 41.8896905], [-87.6257902, 41.8895791], [-87.6257288, 41.8895898], [-87.6255231, 41.8896255], [-87.6255256, 41.8897934], [-87.6254949, 41.8897941], [-87.6254648, 41.889817], [-87.6254664, 41.8898714], [-87.6254461, 41.8898877], [-87.6254474, 41.8899257], [-87.6254684, 41.8899384], [-87.6254701, 41.8899906], [-87.6255035, 41.8900132], [-87.625675, 41.8900085], [-87.6256968, 41.8899927], [-87.6258816, 41.8899874], [-87.6259031, 41.8900027]] }, "parts": [{ "way": 68796725, "version": 10, "coordinates": [[-87.6254648, 41.889817], [-87.6254664, 41.8898714], [-87.6254461, 41.8898877], [-87.6254474, 41.8899257], [-87.6254684, 41.8899384], [-87.6254701, 41.8899906], [-87.6255035, 41.8900132], [-87.625675, 41.8900085], [-87.6256968, 41.8899927], [-87.6258816, 41.8899874], [-87.6259031, 41.8900027], [-87.6260818, 41.8900013], [-87.6261095, 41.8899782], [-87.6261086, 41.8899259], [-87.6261285, 41.8899127], [-87.6261269, 41.8898753], [-87.6261044, 41.8898588], [-87.6261035, 41.8898229], [-87.626103, 41.8898052], [-87.6260771, 41.8897865], [-87.6258989, 41.8897905], [-87.6258799, 41.8898052], [-87.6256906, 41.8898092], [-87.6256664, 41.8897938], [-87.6255256, 41.8897934], [-87.6254949, 41.8897941]], "bottom": 0, "top": 156 }, { "way": 68796733, "version": 5, "coordinates": [[-87.6260771, 41.8897865], [-87.6259425, 41.8896905], [-87.6257902, 41.8895791], [-87.6257288, 41.8895898], [-87.6255231, 41.8896255], [-87.6255256, 41.8897934], [-87.6256664, 41.8897938], [-87.6256906, 41.8898092], [-87.6258799, 41.8898052], [-87.6258989, 41.8897905]], "bottom": 0, "top": 8 }, { "way": 285867425, "version": 1, "coordinates": [[-87.6258543, 41.8899579], [-87.6257153, 41.8899611], [-87.6257111, 41.8898629], [-87.6258501, 41.8898597]], "bottom": 156, "top": 159.7 }] },
|
|
45
46
|
{ "id": "building-hyatt-regency-west-tower", "name": "Hyatt Regency Chicago West Tower", "shortName": "Hyatt West Tower", "height": 111.3, "tipHeight": 111.3, "heightSource": "https://www.skyscrapercenter.com/building/hyatt-regency-chicago-west-tower/13132", "note": "Detailed Hyatt Regency Chicago West Tower: its mapped part of the hotel's outline; a brick slab of 33 storeys, narrow window slots continuous from the lobby to the top two floors on a 3.5 m module between solid corners, and a plain band over them to a flat roof at the Skyscraper Center's 111.3 m. From the photograph's camera it stands between Aon Center and the Blue Cross and Blue Shield Tower, where the drawing's Blue Cross group paints a dark brown sliver.", "footprint": { "way": 235920252, "version": 4, "coordinates": [[-87.6227574, 41.887587], [-87.6228537, 41.887584], [-87.6228521, 41.8874603], [-87.6228518, 41.8874165], [-87.6225427, 41.8874231], [-87.6225408, 41.8875106], [-87.6225253, 41.8875109], [-87.6225266, 41.8875277], [-87.6225277, 41.8875513], [-87.622528, 41.8875746], [-87.6225382, 41.8878947], [-87.62255, 41.8878954], [-87.6225496, 41.8879846], [-87.6228658, 41.8879789], [-87.6228643, 41.8879388], [-87.6228611, 41.8878099], [-87.6227664, 41.8878103]] }, "parts": [{ "way": 235920252, "version": 4, "coordinates": [[-87.6227574, 41.887587], [-87.6228537, 41.887584], [-87.6228521, 41.8874603], [-87.6228518, 41.8874165], [-87.6225427, 41.8874231], [-87.6225408, 41.8875106], [-87.6225253, 41.8875109], [-87.6225266, 41.8875277], [-87.6225277, 41.8875513], [-87.622528, 41.8875746], [-87.6225382, 41.8878947], [-87.62255, 41.8878954], [-87.6225496, 41.8879846], [-87.6228658, 41.8879789], [-87.6228643, 41.8879388], [-87.6228611, 41.8878099], [-87.6227664, 41.8878103]], "bottom": 0, "top": 111.3 }] },
|
|
46
|
-
{ "id": "building-buckingham-east", "name": "Sheraton Grand Chicago Riverwalk", "shortName": "Sheraton Grand", "height": 112.3, "tipHeight": 112.3, "heightSource": "https://www.skyscrapercenter.com/building/sheraton-chicago-hotel-towers/10020", "note": "Detailed Sheraton Grand Chicago Riverwalk: the mapped tower and its corner part; a cream precast tower on an L of punched windows at Emporis's 2.67 m floor-to-floor, its arms to 94.76 m, the corner's round tower two floors higher to 100.1 m, and a drum of cream and maroon fins on each round end, the corner's to the Skyscraper Center's 112.3 m. Heights stand on the Skyscraper Center's datum: its top floor at 97 m, a floor-to-roof of 3.1 m and 12.2 m drums as Emporis gives them; the published height nearest the drawing's reading, over Emporis's 330.56 ft. The Skyscraper Center's 112.8 m tip, 50 cm over the drum, is not modelled. The drawing's buckingham-east group is its corner's lit drum over its tower.", "footprint": { "way": 592122464, "version": 5, "coordinates": [[-87.6199499, 41.8891119], [-87.6199176, 41.8893381], [-87.6199306, 41.8893589], [-87.6199502, 41.8893716], [-87.6199845, 41.8893878], [-87.6200276, 41.8893957], [-87.6200732, 41.8893915], [-87.6201147, 41.8893744], [-87.6201372, 41.8893482], [-87.6201463, 41.8893162], [-87.6201584, 41.8892447], [-87.6201841, 41.889063], [-87.6201875, 41.8890302], [-87.6201812, 41.8889946], [-87.6201588, 41.8889641], [-87.620121, 41.8889384], [-87.6200743, 41.8889276], [-87.6196505, 41.8888958], [-87.6196025, 41.8889162], [-87.6195726, 41.8889427], [-87.6195591, 41.8889735], [-87.6195627, 41.8890068], [-87.6195846, 41.8890373], [-87.6196228, 41.8890638], [-87.6198263, 41.8890777], [-87.6198225, 41.889104]] }, "parts": [{ "way": 592122464, "version": 5, "coordinates": [[-87.6199499, 41.8891119], [-87.6199176, 41.8893381], [-87.6199306, 41.8893589], [-87.6199502, 41.8893716], [-87.6199845, 41.8893878], [-87.6200276, 41.8893957], [-87.6200732, 41.8893915], [-87.6201147, 41.8893744], [-87.6201372, 41.8893482], [-87.6201463, 41.8893162], [-87.6201584, 41.8892447], [-87.6201841, 41.889063], [-87.6201875, 41.8890302], [-87.6201812, 41.8889946], [-87.6201588, 41.8889641], [-87.620121, 41.8889384], [-87.6200743, 41.8889276], [-87.6196505, 41.8888958], [-87.6196025, 41.8889162], [-87.6195726, 41.8889427], [-87.6195591, 41.8889735], [-87.6195627, 41.8890068], [-87.6195846, 41.8890373], [-87.6196228, 41.8890638], [-87.6198263, 41.8890777], [-87.6198225, 41.889104]], "bottom": 0, "top": 94.76 }, { "way": 1269924307, "version": 1, "coordinates": [[-87.6199691, 41.8890378], [-87.6199677, 41.889013], [-87.6199719, 41.8889952], [-87.6199903, 41.8889744], [-87.6200135, 41.88896], [-87.6200416, 41.8889517], [-87.6200717, 41.8889503], [-87.6201037, 41.8889568], [-87.6201308, 41.8889711], [-87.6201498, 41.8889914], [-87.6201583, 41.8890152], [-87.6201555, 41.8890398], [-87.6201415, 41.8890622], [-87.6201241, 41.8890776], [-87.6200882, 41.8890904], [-87.6200552, 41.8890928], [-87.6200258, 41.8890875], [-87.6200001, 41.8890757], [-87.6199805, 41.8890586]], "bottom": 94.76, "top": 100.1 }] },
|
|
47
|
+
{ "id": "building-buckingham-east", "name": "Sheraton Grand Chicago Riverwalk", "shortName": "Sheraton Grand", "height": 112.3, "tipHeight": 112.3, "heightSource": "https://www.skyscrapercenter.com/building/sheraton-chicago-hotel-towers/10020", "note": "Detailed Sheraton Grand Chicago Riverwalk: the mapped tower and its corner part; a cream precast tower on an L of punched windows at Emporis's 2.67 m floor-to-floor, its arms to 94.76 m, the corner's round tower two floors higher to 100.1 m, and a drum of cream and maroon fins on each round end, the corner's to the Skyscraper Center's 112.3 m. Heights stand on the Skyscraper Center's datum: its top floor at 97 m, a floor-to-roof of 3.1 m and 12.2 m drums as Emporis gives them; the published height nearest the drawing's reading, over Emporis's 330.56 ft. The Skyscraper Center's 112.8 m tip, 50 cm over the drum, is not modelled. The drawing's buckingham-east group is its corner's lit drum over its tower. The mapped east wing and its round end continue the hotel to the east, with estimated roofs at 84.08 m and 86.75 m on the same height datum and a fourth crown. Photographs from before 2013 establish the wing; exact roof and crown dimensions remain inferred.", "footprint": { "way": 592122464, "version": 5, "coordinates": [[-87.6199499, 41.8891119], [-87.6199176, 41.8893381], [-87.6199306, 41.8893589], [-87.6199502, 41.8893716], [-87.6199845, 41.8893878], [-87.6200276, 41.8893957], [-87.6200732, 41.8893915], [-87.6201147, 41.8893744], [-87.6201372, 41.8893482], [-87.6201463, 41.8893162], [-87.6201584, 41.8892447], [-87.6201841, 41.889063], [-87.6201875, 41.8890302], [-87.6201812, 41.8889946], [-87.6201588, 41.8889641], [-87.620121, 41.8889384], [-87.6200743, 41.8889276], [-87.6196505, 41.8888958], [-87.6196025, 41.8889162], [-87.6195726, 41.8889427], [-87.6195591, 41.8889735], [-87.6195627, 41.8890068], [-87.6195846, 41.8890373], [-87.6196228, 41.8890638], [-87.6198263, 41.8890777], [-87.6198225, 41.889104]] }, "parts": [{ "way": 592122464, "version": 5, "coordinates": [[-87.6199499, 41.8891119], [-87.6199176, 41.8893381], [-87.6199306, 41.8893589], [-87.6199502, 41.8893716], [-87.6199845, 41.8893878], [-87.6200276, 41.8893957], [-87.6200732, 41.8893915], [-87.6201147, 41.8893744], [-87.6201372, 41.8893482], [-87.6201463, 41.8893162], [-87.6201584, 41.8892447], [-87.6201841, 41.889063], [-87.6201875, 41.8890302], [-87.6201812, 41.8889946], [-87.6201588, 41.8889641], [-87.620121, 41.8889384], [-87.6200743, 41.8889276], [-87.6196505, 41.8888958], [-87.6196025, 41.8889162], [-87.6195726, 41.8889427], [-87.6195591, 41.8889735], [-87.6195627, 41.8890068], [-87.6195846, 41.8890373], [-87.6196228, 41.8890638], [-87.6198263, 41.8890777], [-87.6198225, 41.889104]], "bottom": 0, "top": 94.76 }, { "way": 1269924307, "version": 1, "coordinates": [[-87.6199691, 41.8890378], [-87.6199677, 41.889013], [-87.6199719, 41.8889952], [-87.6199903, 41.8889744], [-87.6200135, 41.88896], [-87.6200416, 41.8889517], [-87.6200717, 41.8889503], [-87.6201037, 41.8889568], [-87.6201308, 41.8889711], [-87.6201498, 41.8889914], [-87.6201583, 41.8890152], [-87.6201555, 41.8890398], [-87.6201415, 41.8890622], [-87.6201241, 41.8890776], [-87.6200882, 41.8890904], [-87.6200552, 41.8890928], [-87.6200258, 41.8890875], [-87.6200001, 41.8890757], [-87.6199805, 41.8890586]], "bottom": 94.76, "top": 100.1 }, { "way": 1269924305, "version": 1, "coordinates": [[-87.6191033, 41.8890226], [-87.6190622, 41.8889954], [-87.6190428, 41.8889627], [-87.6190431, 41.8889236], [-87.6190607, 41.8888928], [-87.619091, 41.8888724], [-87.6191278, 41.8888566], [-87.6193634, 41.8888743], [-87.6196505, 41.8888958], [-87.6196025, 41.8889162], [-87.6195726, 41.8889427], [-87.6195591, 41.8889735], [-87.6195627, 41.8890068], [-87.6195846, 41.8890373], [-87.6196228, 41.8890638]], "bottom": 0, "top": 84.08, "estimatedHeight": true }, { "way": 1269924304, "version": 1, "coordinates": [[-87.619112, 41.8888866], [-87.61909, 41.8888996], [-87.6190746, 41.888917], [-87.6190672, 41.8889372], [-87.6190685, 41.8889581], [-87.6190786, 41.8889777], [-87.6190963, 41.8889939], [-87.6191199, 41.8890052], [-87.6191471, 41.8890105], [-87.6191778, 41.8890088], [-87.6192059, 41.8889994], [-87.619228, 41.8889835], [-87.6192416, 41.8889629], [-87.6192449, 41.8889401], [-87.6192376, 41.8889179], [-87.6192207, 41.8888988], [-87.6191959, 41.8888851], [-87.6191665, 41.8888785], [-87.6191384, 41.8888793]], "bottom": 84.08, "top": 86.75, "estimatedHeight": true }], "groundFootprintWays": [592122464, 1269924305] },
|
|
47
48
|
{ "id": "building-three-illinois-center", "name": "Three Illinois Center", "shortName": "Three Illinois Center", "height": 106.7, "tipHeight": 106.7, "heightSource": "https://www.skyscrapercenter.com/building/three-illinois-center/13913", "note": "Detailed Three Illinois Center: the mapped outline; a dark bronze curtain wall of 30 storeys, mullions on a 5 ft module and glass over a spandrel on each floor at the published 11.5 ft slab to slab over a 5.06 m lobby, and the top two floors' windowless mechanical penthouse, glazed about 2019 but kept as the 2013 photograph shows it, under a flat roof at the Skyscraper Center's and Emporis's 350 ft. Its east end fills the gap between 340 on the Park and The Buckingham, where the drawing's buckingham-west group paints an office tower whose top stands about 140 layer units higher.", "footprint": { "way": 95486958, "version": 12, "coordinates": [[-87.6204966, 41.8869542], [-87.6202534, 41.8869566], [-87.6200384, 41.8869571], [-87.6200396, 41.887005], [-87.6200455, 41.8873733], [-87.620058, 41.8876286], [-87.6205101, 41.8876189], [-87.6205054, 41.8873683]] }, "parts": [{ "way": 95486958, "version": 12, "coordinates": [[-87.6204966, 41.8869542], [-87.6202534, 41.8869566], [-87.6200384, 41.8869571], [-87.6200396, 41.887005], [-87.6200455, 41.8873733], [-87.620058, 41.8876286], [-87.6205101, 41.8876189], [-87.6205054, 41.8873683]], "bottom": 0, "top": 106.7 }] },
|
|
48
49
|
{ "id": "building-swissotel", "name": "Swissôtel Chicago", "shortName": "Swissôtel", "height": 139.3, "tipHeight": 139.3, "heightSource": "https://www.skyscrapercenter.com/building/swissotel-chicago/4362", "note": "Detailed Swissôtel Chicago: the hotel's mapped tower part, a triangle; a flush grid of reflective glass, thin mullions about 1.5 m apart and a transom at each of OpenStreetMap's 45 levels, under a flat roof at the Skyscraper Center's 139.3 m. It stands behind Three Illinois Center and The Buckingham from the drawing's camera, and fills the seam where their corners meet. The hotel's outline, 95486966, adds a small block on the tower's north-east face, and its podium, 95486953, carries no height; neither is modelled.", "footprint": { "way": 641288601, "version": 3, "coordinates": [[-87.6194102, 41.8874254], [-87.619534, 41.8875706], [-87.619676, 41.8873866], [-87.6198184, 41.8871906], [-87.6192311, 41.8871971], [-87.619375, 41.8873843]] }, "parts": [{ "way": 641288601, "version": 3, "coordinates": [[-87.6194102, 41.8874254], [-87.619534, 41.8875706], [-87.619676, 41.8873866], [-87.6198184, 41.8871906], [-87.6192311, 41.8871971], [-87.619375, 41.8873843]], "bottom": 0, "top": 139.3 }] },
|
|
49
50
|
];
|
|
@@ -56,6 +56,23 @@ export function footprintMetrics(coordinates) {
|
|
|
56
56
|
}, 0)) / 2;
|
|
57
57
|
return { min, max, center: min.map((n, i) => (n + max[i]) / 2), size: min.map((n, i) => max[i] - n), area };
|
|
58
58
|
}
|
|
59
|
+
// Some complexes have separately mapped wings beyond the primary way. Keep
|
|
60
|
+
// their source ways intact, and opt in only disjoint ground parts of this model.
|
|
61
|
+
export function groundFootprints(record) {
|
|
62
|
+
if (!record.groundFootprintWays)
|
|
63
|
+
return [record.footprint];
|
|
64
|
+
return record.groundFootprintWays.map((way) => {
|
|
65
|
+
const part = record.parts.find((entry) => entry.way === way && entry.bottom === 0);
|
|
66
|
+
if (!part)
|
|
67
|
+
throw new Error(`Missing ground footprint ${way} for ${record.id}`);
|
|
68
|
+
return part;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
export function buildingGroundBounds(record) {
|
|
72
|
+
// Only extrema are meaningful for concatenated rings, not a polygon's area.
|
|
73
|
+
const { min, max, center, size } = footprintMetrics(groundFootprints(record).flatMap((part) => part.coordinates));
|
|
74
|
+
return { min, max, center, size };
|
|
75
|
+
}
|
|
59
76
|
function plan(coordinates) {
|
|
60
77
|
const points = coordinates.map((p) => { const [east, north] = projectGround(p); return [east, -north]; });
|
|
61
78
|
const area = points.reduce((sum, a, i) => {
|
|
@@ -313,8 +330,20 @@ export function createGeographicGround(offset = [0, 0]) {
|
|
|
313
330
|
group.add(streets);
|
|
314
331
|
const outlines = [];
|
|
315
332
|
for (const record of geographicBuildings) {
|
|
316
|
-
const
|
|
317
|
-
|
|
333
|
+
const edges = new Map();
|
|
334
|
+
for (const footprint of groundFootprints(record)) {
|
|
335
|
+
const points = footprint.coordinates.map((p) => { const [x, y] = projectGround(p); return [x, 0.1, -y]; });
|
|
336
|
+
points.forEach((p, i) => {
|
|
337
|
+
const q = points[(i + 1) % points.length];
|
|
338
|
+
const key = [p.join(","), q.join(",")].sort().join(";");
|
|
339
|
+
const prior = edges.get(key);
|
|
340
|
+
edges.set(key, { points: [p, q], count: (prior?.count ?? 0) + 1 });
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
// Shared mapped boundaries are inside the complex, not its ground outline.
|
|
344
|
+
for (const edge of edges.values())
|
|
345
|
+
if (edge.count === 1)
|
|
346
|
+
outlines.push(...edge.points);
|
|
318
347
|
}
|
|
319
348
|
const footprints = lines(outlines, 0xc9c9c9);
|
|
320
349
|
group.add(footprints);
|
package/site/skyline-3d.html
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
<div class="loading" id="loading" role="status">
|
|
16
16
|
<span class="visually-hidden">Preparing the skyline…</span>
|
|
17
17
|
<!-- skyline-loading:start -->
|
|
18
|
-
<svg class="skyline-trace" viewBox="0 0 1000
|
|
19
|
-
<defs><path id="skyline-loading-outline" pathLength="1" d="M0
|
|
18
|
+
<svg class="skyline-trace" viewBox="0 0 1000 379.13" aria-hidden="true" focusable="false">
|
|
19
|
+
<defs><path id="skyline-loading-outline" pathLength="1" d="M0 379.13 L24 379.13 L24 317.67 L36.32 311.43 L74.67 311.43 L77.22 312.72 L77.46 311.68 L85.82 311.68 L86.06 307.48 L130.22 307.99 L130.45 308.97 L130.91 308.97 L131.15 307.99 L160.43 307.99 L160.9 312.71 L176.7 312.71 L176.93 319.92 L184.6 319.92 L185.53 321.04 L202.27 321.04 L202.5 319.71 L203.2 319.28 L203.66 322.05 L206.68 328.57 L206.92 325.5 L208.31 325.5 L208.54 334.72 L210.63 334.72 L210.87 338.85 L228.3 338.85 L228.53 310.05 L232.25 310.05 L232.48 300.18 L236.2 300.18 L236.43 273.16 L238.29 273.16 L238.53 265.43 L239.46 265.43 L239.69 264.58 L242.94 264.58 L243.17 265.69 L245.03 265.69 L245.27 264.58 L247.13 264.58 L247.36 265.69 L248.52 265.43 L248.75 264.58 L250.84 264.58 L251.08 265.43 L251.77 265.43 L252.24 273.16 L253.87 273.16 L254.1 310.05 L254.56 307.48 L256.65 307.48 L256.89 310.05 L258.05 310.05 L258.28 307.48 L260.84 307.48 L261.07 324.55 L265.25 324.55 L265.49 312.2 L267.11 312.2 L267.35 305.33 L281.29 305.33 L281.52 308.77 L292.45 308.77 L292.68 302.35 L303.14 302.32 L303.37 223.64 L314.29 223.64 L314.53 216.27 L321.96 216.27 L322.2 214.04 L339.86 214.22 L340.09 223.64 L342.88 223.64 L343.12 242.7 L371.7 242.7 L371.94 372.43 L376.35 372.43 L376.58 255.25 L395.41 227.09 L396.11 226.91 L398.9 245.87 L399.36 245.89 L399.59 226.91 L400.06 226.91 L427.02 256.11 L427.25 305.08 L432.13 305.08 L432.37 197.36 L499.54 197.36 L499.77 221.93 L513.71 221.93 L513.95 75.03 L533.24 75.03 L533.47 66.02 L545.79 66.02 L546.72 16 L547.88 16 L548.81 66.02 L555.78 66.02 L556.01 81.13 L556.94 81.13 L557.18 149.08 L563.45 140.02 L563.92 125.57 L564.38 124.48 L564.61 118.88 L565.78 140.14 L579.02 159.17 L581.81 168.47 L582.04 171.95 L583.67 171.95 L583.9 175.35 L585.3 175.35 L585.53 178.75 L586.93 178.75 L587.16 182.14 L588.55 182.14 L588.79 327.64 L589.25 369.24 L591.81 369.24 L592.04 87.39 L605.29 87.39 L605.52 81.98 L623.65 81.98 L623.88 68.08 L624.81 68.08 L625.04 81.98 L643.17 81.98 L643.4 87.39 L656.42 87.39 L656.65 197.22 L656.88 184.61 L727.31 184.61 L727.77 197.22 L734.28 197.22 L734.51 203.31 L777.98 203.31 L778.44 211.51 L778.67 287.57 L783.55 287.57 L783.79 277.02 L795.41 277.02 L795.64 274.53 L813.77 274.53 L814 277.02 L824.23 277.02 L824.46 283.38 L824.93 283.38 L825.16 287.57 L830.74 287.57 L830.97 259.6 L868.85 259.6 L869.09 379.13 L910.69 379.13 L910.92 297.82 L912.78 297.82 L913.01 282.77 L926.73 282.77 L926.96 297.82 L931.38 297.82 L931.61 287.35 L946.48 287.35 L946.71 297.82 L948.34 297.82 L948.57 306.98 L961.13 306.98 L961.36 294.22 L973.91 294.22 L974.37 306.98 L976 306.98 L976 379.13 L1000 379.13" /></defs>
|
|
20
20
|
<use class="skyline-trace-rail" href="#skyline-loading-outline" />
|
|
21
21
|
<use class="skyline-trace-line" href="#skyline-loading-outline" />
|
|
22
22
|
<use class="skyline-trace-tip" href="#skyline-loading-outline" />
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as THREE from "./vendor/three-r186.js";
|
|
2
2
|
import { geographicBuildings } from "./models/skyline-geography-data.js";
|
|
3
|
-
import {
|
|
3
|
+
import { buildingGroundBounds, groundFootprints, geographicOrigin } from "./models/skyline-geography.js";
|
|
4
4
|
import { createGeographicSkyline } from "./skyline-scene.js";
|
|
5
5
|
export { createGeographicSkyline, skylineAzimuth } from "./skyline-scene.js";
|
|
6
6
|
// The drawing has no geographic position. Register the two scenes at Crain's
|
|
@@ -42,7 +42,7 @@ export function createSkylineComparison(models, anchor) {
|
|
|
42
42
|
[point.x, point.z].forEach((n, axis) => { min[axis] = Math.min(min[axis], n); max[axis] = Math.max(max[axis], n); });
|
|
43
43
|
}
|
|
44
44
|
});
|
|
45
|
-
const footprint =
|
|
45
|
+
const footprint = buildingGroundBounds(record);
|
|
46
46
|
const row = document.createElement("tr");
|
|
47
47
|
const name = document.createElement("th");
|
|
48
48
|
name.scope = "row";
|
|
@@ -50,6 +50,14 @@ export function createSkylineComparison(models, anchor) {
|
|
|
50
50
|
link.href = `https://www.openstreetmap.org/way/${record.footprint.way}`;
|
|
51
51
|
link.textContent = record.shortName;
|
|
52
52
|
name.append(link);
|
|
53
|
+
for (const part of groundFootprints(record)) {
|
|
54
|
+
if (part.way === record.footprint.way)
|
|
55
|
+
continue;
|
|
56
|
+
const extra = document.createElement("a");
|
|
57
|
+
extra.href = `https://www.openstreetmap.org/way/${part.way}`;
|
|
58
|
+
extra.textContent = `part ${part.way}`;
|
|
59
|
+
name.append(" · ", extra);
|
|
60
|
+
}
|
|
53
61
|
row.append(name);
|
|
54
62
|
const values = [
|
|
55
63
|
`${signed(footprint.center[0])}, ${signed(footprint.center[1])}`,
|