-
Notifications
You must be signed in to change notification settings - Fork 14
/
index.html
246 lines (234 loc) · 10.8 KB
/
index.html
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://cesium.com/downloads/cesiumjs/releases/1.70.1/Build/Cesium/Cesium.js"></script>
<script src="https://unpkg.com/h3-js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.0/chroma.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/benchmark/2.1.4/benchmark.min.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.70.1/Build/Cesium/Widgets/widgets.css"
rel="stylesheet">
<link href="index.css" rel="stylesheet">
<title>Starlink daily coverage estimates</title>
</head>
<body>
<h1>UPDATE:</h1>
<h2>THIS PROJECT IS NOT BEING UPDATED</h2>
<p class="description">
For a much more in-depth simulation check out <a href="https://mikepuchol.com/modeling-starlink-capacity-843b2387f501">https://mikepuchol.com/modeling-starlink-capacity-843b2387f501</a>
<br>
I am only making changes to this project keep the page working with the historical data.
</p>
<p class="description">
I am now providing two different data sets: one that is calculated witha 25 degree minimum user terminal angle
and one with a 35 degree minimum user terminal angle. The more recent SpaceX filings show that they are capable
of using the 25 degree minimum but that they eventually want to increase that. The previous datasets were
calculated with a 35 degree minimum as that was what I had found on file at the time. With the lower minimum
angle and about 100 more satellites at operational altitudes the 25 degree dataset now shows bands with
continuous coverage.
</p>
<p class="description">
Furthermore, I plan to calculate what I call the <em>satellite multiplicity</em>, the number of satellites
overhead at any given time on average, since the number of overlapping satellites is relevant to some
calculations for routing and bandwith.
</p>
<p class="description">
New Features: You can now double-click a cell to toggle showing more granular data.
Cells with 100% uptime now display a gold star. Faster parsing, reduced battery drain,
hopefully.
</p>
<div id="toolbar">
<label for="minAngleSelect">Choose a minimum angle to load the dataset:</label>
<select name="minAngle" id="minAngleSelect" onchange="loadDataset(this.value)">
<option value="25">25 degrees</option>
<option value="35">35 degrees</option>
</select>
</div>
<div id="cesiumContainer" class="fullSize"></div>
<script>
const total = 1440;
const colorScale = chroma.scale('RdYlBu').domain([0, total]);
const maxBlue = colorScale(total).gl();
let zoomedCell = {};
const OPACITY = 0.7;
let uptimeMaterial = new Cesium.ImageMaterialProperty({
image: 'Gold_Star.png',
transparent: true,
});
var viewer = new Cesium.Viewer('cesiumContainer', {
imageryProvider: new Cesium.UrlTemplateImageryProvider({
url: 'https://stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}.png',
credit: 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under ODbL.'
}),
baseLayerPicker: false,
geocoder: false,
animation: false,
timeline: false,
requestRenderMode: true,
});
function renderHexes() {
let cells = h3.polygonToCells(rect.coordinates, 4, true);
for (const cell of cells) {
let bounds = h3.cellToBoundary(cell, true).flat();
let rgb = colorScale(coverage[cell]).gl();
viewer.entities.add({
name: cell,
polygon: {
hierarchy: Cesium.Cartesian3.fromDegreesArray(bounds),
material: new Cesium.Color(rgb[0], rgb[1], rgb[2], OPACITY),
height: 0,
outline: true,
outlineColor: Cesium.Color.BLACK,
},
})
}
}
function calcAverage(cell) {
const baseRes = h3.getResolution(cell);
if (baseRes > 3) {
console.error("Can't calculate at higher resolutions!");
return;
}
const res4_children = h3.cellToChildren(cell, 4);
let total = 0;
for (const child of res4_children) {
total += coverage[child];
}
return total / res4_children.length;
}
function renderCells(cells) {
for (const cell of cells) {
let bounds = h3.cellToBoundary(cell, true).flat();
const res = h3.getResolution(cell);
let avg = 0;
if (res === 2) {
avg = calcAverage(cell);
} else {
avg = coverage[cell];
}
let mat;
const rgb = colorScale(avg).gl();
viewer.entities.add({
id: "" + cell,
name: `cell: ${cell.slice(0, -8)}`,
description: `This cell is covered on average ${Math.round(avg) | 0} minutes of ${total} for the day. <br/>
This is approximately ${Math.round((avg / total) * 1000) / 10}% of the day.`,
polygon: {
hierarchy: Cesium.Cartesian3.fromDegreesArray(bounds),
material: (avg > 1439.9 ? uptimeMaterial : new Cesium.Color(rgb[0], rgb[1], rgb[2], OPACITY)),
height: 0,
outline: true,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 1.0,
},
})
}
}
function renderSubHexes(evt) {
let ellipsoid = viewer.scene.globe.ellipsoid;
let cartesian = viewer.camera.pickEllipsoid(evt.position, ellipsoid);
let lat, long;
if (cartesian) {
let cartographic = ellipsoid.cartesianToCartographic(cartesian);
long = Cesium.Math.toDegrees(cartographic.longitude);
lat = Cesium.Math.toDegrees(cartographic.latitude);
} else {
return;
}
cell = h3.latLngToCell(lat, long, 2);
res4Cells = h3.cellToChildren(cell, 4);
// If we zoomed this cell, revert it
if (zoomedCell[cell]) {
zoomedCell[cell] = undefined;
for (const cellid of res4Cells) {
viewer.entities.removeById(cellid);
}
renderCells([cell]);
} else {
zoomedCell[cell] = true;
let toRemove = viewer.selectedEntity;
viewer.selectedEntity = undefined;
viewer.entities.remove(toRemove);
renderCells(res4Cells);
}
}
function onReady() {
const res0 = h3.getRes0Cells();
let res2 = [];
for (const cell of res0) {
res2_cells = h3.cellToChildren(cell, 2);
res2.push(res2_cells);
}
res2 = res2.flat();
// viewer.entities.suspendEvents();
viewer.entities.removeAll();
renderCells(res2);
// viewer.entities.resumeEvents();
scene.requestRender();
}
let scene = viewer.scene;
scene.skyAtmosphere.show = false;
scene.fog.enabled = false;
scene.globe.showGroundAtmosphere = false;
if (!scene.pickPositionSupported) {
window.alert("This browser does not support pickPosition.");
}
// Remove the default doubleclick action so we can put ours in
viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
let handler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
handler.setInputAction(movement => {
viewer.entities.suspendEvents();
renderSubHexes(movement);
viewer.entities.resumeEvents();
scene.requestRender();
}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK);
let coverage = {};
function loadDataset(angle) {
fetch(`./h3_4_cov_op_${angle}.bin`)
.then(resp => {
resp.arrayBuffer().then(buffer => {
coverage = {};
let arr = new Uint32Array(buffer);
for (var i = 0; i < arr.length - 1; i += 2) {
coverage[`${arr[i].toString(16)}ffffffff`] = arr[i + 1];
}
zoomedCell = {};
onReady();
});
})
.catch(reason => console.error("error" + reason));
}
loadDataset(document.getElementById('minAngleSelect').value);
</script>
<div class="description">
<p>This renders the percentage of the day that a portion of the earth is covered by a Starlink satellite. (Red
is no
coverage, blue is all day coverage).</p>
<p>Click on a cell to see how many minutes in a day a cell is covered by at least one satellite. Note that
currently
for performance reasons the cells are much larger than the resolution the data was simulated at. As such the
error within a cell can be quite large, especially near the poles where the coverage drops off rapidly.
</p>
<p>In my opinion anything less than 1440 minutes (aka continuous coverage) means that SpaceX <em>should not</em>
provide service in that area.</p>
For more info about how it's calculated:
<a href="https://github.com/sebsebmc/starlink-coverage">https://github.com/sebsebmc/starlink-coverage</a>
<h2> Changelog:</h2>
<ul>
<li>6-20-20 - Removed satellites that had low altitudes because they are not believed to be in operational
orbits.</li>
<li>6-21-20 - Fixed remaining issues with calculations at the antimerdian.</li>
<li>7-31-20 - <ul>
<li>Added a double-click option to view higher resolution data. Double-click again to revert.</li>
<li>Added a 25 minimum degree dataset</li>
<li>Use gold star to mark cells with 100% uptime</li>
<li>Switched to new format and explicit rendering</li>
</ul>
</li>
</ul>
<p>
Star image: Yakiv Gluck / CC BY-SA (https://creativecommons.org/licenses/by-sa/3.0)
</p>
</div>
</body>
</html>