🤬
  • ■ ■ ■ ■ ■ ■
    RIT.js
     1 +// This is the source code for the Bellingcat Radar Interference Tracker.
     2 +// The tool provides a user interface which enables the analysis of Radio Frequency Interference (RFI)
     3 +// Most of the RFI caused by ground-based systems are military radars
     4 +// Investigating the spatial and temporal characteristics of the signal can yield information on the deployment of military radar systems
     5 + 
     6 +// Below is an overview of the 10 sections
     7 + 
     8 +// 1. Load Data
     9 +// 2. Configure Map
     10 +// 3. Set up user interface panel
     11 +// 4. Create image aggregation dropdown
     12 +// 5. Create opacity slider
     13 +// 6. Create date selector
     14 +// 7. Create RFI chart
     15 +// 8. Create "Visit Example Locations" dropdown
     16 +// 9. Map Setup
     17 +// 10. Initialize App
     18 + 
     19 +// For queries, please contact [email protected]
     20 + 
     21 +// --------------------- Step 1: Load Data --------------------------------
     22 + 
     23 +// Load country outlines
     24 +var countries = ee.FeatureCollection("FAO/GAUL_SIMPLIFIED_500m/2015/level0");
     25 + 
     26 +var country_outlines = ee.Image().byte().paint({
     27 + featureCollection: countries,
     28 + width: 3,
     29 + color: "FFFFFF",
     30 +});
     31 + 
     32 +// Load sentinel-1 imagery
     33 +var sentinel1 = ee.ImageCollection("COPERNICUS/S1_GRD");
     34 + 
     35 +// RFI primarily shows up in the VH polarization, but including VV lets you create an RGB visualization and distinguish RFI from background imagery better
     36 +var vh = sentinel1
     37 + // Filter to get images with VV and VH dual polarization.
     38 + .filter(ee.Filter.listContains("transmitterReceiverPolarisation", "VH"))
     39 + .filter(ee.Filter.listContains("transmitterReceiverPolarisation", "VV"))
     40 + // Filter to get images collected in interferometric wide swath mode.
     41 + .filter(ee.Filter.eq("instrumentMode", "IW"));
     42 + 
     43 +// Filter to get images from different look angles.
     44 +var vhA = vh.filter(ee.Filter.eq("orbitProperties_pass", "ASCENDING"));
     45 +var vhD = vh.filter(ee.Filter.eq("orbitProperties_pass", "DESCENDING"));
     46 + 
     47 +// --------------------- Step 2: Configure Map --------------------------------
     48 + 
     49 +// Create the main map
     50 +var mapPanel = ui.Map();
     51 +var layers = mapPanel.layers();
     52 + 
     53 +// Creating a country outline layer that can be added later
     54 +var country_layer = ui.Map.Layer(
     55 + country_outlines,
     56 + { palette: "FFFFFF", min: 0, max: 1 },
     57 + "country outlines",
     58 + true
     59 +);
     60 + 
     61 +// Remove unnecessary map functionalities
     62 +mapPanel.setControlVisibility({
     63 + all: false,
     64 + zoomControl: true,
     65 + mapTypeControl: true,
     66 +});
     67 + 
     68 +// --------------------- Step 3: Set up the User Interface Panel --------------------------------
     69 + 
     70 +// Create the main panel
     71 +var inspectorPanel = ui.Panel({ style: { width: "30%" } });
     72 + 
     73 +// Create an intro panel with labels.
     74 +var intro = ui.Panel([
     75 + ui.Label({
     76 + value: "Bellingcat Radar Interference Tracker",
     77 + style: { fontSize: "20px", fontWeight: "bold" },
     78 + }),
     79 + ui.Label(
     80 + "This map shows interference from ground based radar systems as red and blue streaks. Most of these are military radars. Click on the map to generate a historical graph of Radio Frequency Interference (RFI) at a particular location:"
     81 + ),
     82 +]);
     83 + 
     84 +// --------------------- Step 4: Create imagery aggregation menu --------------------------------
     85 + 
     86 +// Create UI label for the dropdown menu
     87 +var layerLabel = ui.Label("Display imagery aggregated by:");
     88 + 
     89 +// Layer visualization dictionary
     90 +var layerProperties = {
     91 + Day: {
     92 + name: "Day",
     93 + defaultVisibility: false,
     94 + },
     95 + Month: {
     96 + name: "Month",
     97 + defaultVisibility: true,
     98 + },
     99 + Year: {
     100 + name: "Year",
     101 + defaultVisibility: false,
     102 + },
     103 +};
     104 + 
     105 +// Get keys from dictionary
     106 +var selectItems = Object.keys(layerProperties);
     107 + 
     108 +// Create dropdown menu to toggle between imagery aggregated at different timescales
     109 +var layerSelect = ui.Select({
     110 + items: selectItems,
     111 + value: selectItems[1],
     112 + onChange: function (selected) {
     113 + // Loop through the map layers and compare the selected element to the name
     114 + // of the layer. If they're the same, show the layer and set the
     115 + // corresponding legend. Hide the others.
     116 + mapPanel.layers().forEach(function (element, index) {
     117 + element.setShown(selected == element.getName());
     118 + 
     119 + var dict = {
     120 + Day: "daily",
     121 + Month: "monthly",
     122 + Year: "yearly",
     123 + };
     124 + 
     125 + // Add a line that provides information on the level of aggregation of the imagery currently being shown
     126 + image_info.setValue(
     127 + "You are currently viewing " +
     128 + dict[selected] +
     129 + " Sentinel-1 imagery from "
     130 + );
     131 + });
     132 + },
     133 +});
     134 + 
     135 +// --------------------- Step 5: Create Opacity Slider --------------------------------
     136 + 
     137 +var opacitySlider = ui.Slider({
     138 + min: 0,
     139 + max: 1,
     140 + value: 1,
     141 + step: 0.01,
     142 +});
     143 +opacitySlider.onSlide(function (value) {
     144 + mapPanel.layers().forEach(function (element, index) {
     145 + element.setOpacity(value);
     146 + });
     147 +});
     148 + 
     149 +var opacityLabel = ui.Label("Opacity: ");
     150 + 
     151 +// Create panel to hold the aggregation dropdown menu and the opacity slider
     152 + 
     153 +var viewPanel = ui.Panel({
     154 + widgets: [layerLabel, layerSelect, opacityLabel, opacitySlider],
     155 + style: { stretch: "horizontal" },
     156 + layout: ui.Panel.Layout.Flow("horizontal"),
     157 +});
     158 + 
     159 +// --------------------- Step 6: Create Date Selector --------------------------------
     160 + 
     161 +// Get date range for Sentinel-1 imagery, backdate current date by one week to ensure imagery is available
     162 +var start = ee.Date(sentinel1.first().get("system:time_start"));
     163 +var now = ee.Date(Date.now()).advance(-1, "week");
     164 + 
     165 +// Format date to display it to the user
     166 +var date = ui.Label(now.format("MMMM dd, YYYY").getInfo());
     167 +var image_info = ui.Label(
     168 + "You are currently viewing monthly Sentinel-1 imagery from "
     169 +);
     170 + 
     171 +// Run this function on a change of the dateSlider.
     172 + 
     173 +var slide = function (range) {
     174 + date.setValue(ee.Date(range.start()).format("MMMM dd, YYYY").getInfo());
     175 + 
     176 + // From the selected date, get the year and month for aggregation
     177 + var year = range.start().getRange("year");
     178 + var month = range.start().getRange("month");
     179 + 
     180 + // Get imagery for the month/year, disaggregated by ascending/descending orbital trajectory
     181 + var vhA_monthly = vhA.filterDate(month.start(), month.end());
     182 + var vhD_monthly = vhD.filterDate(month.start(), month.end());
     183 + 
     184 + var vhA_annual = vhA.filterDate(year.start(), year.end());
     185 + var vhD_annual = vhD.filterDate(year.start(), year.end());
     186 + 
     187 + // Create a composite at different polarizations and look angles.
     188 + // Note: we're selecitng the maximum values for each time period-- this bring out the RFI
     189 + var comp_monthly = ee.Image.cat([
     190 + vhA_monthly.select("VH").max(),
     191 + ee
     192 + .ImageCollection(vhA_monthly.select("VV").merge(vhD_monthly.select("VV")))
     193 + .max(),
     194 + vhD_monthly.select("VH").max(),
     195 + ]);
     196 + 
     197 + var comp_annual = ee.Image.cat([
     198 + vhA_annual.select("VH").max(),
     199 + ee
     200 + .ImageCollection(vhA_annual.select("VV").merge(vhD_annual.select("VV")))
     201 + .max(),
     202 + vhD_annual.select("VH").max(),
     203 + ]);
     204 + 
     205 + // Create layers, and visualize based on which value is selected in the dropdown menu
     206 + var daily = ui.Map.Layer(
     207 + vh.filterDate(range.start(), range.end()),
     208 + { min: [-25, -20, -25], max: [0, 10, 0], opacity: 0.8 },
     209 + "Day",
     210 + "Day" == layerSelect.getValue()
     211 + );
     212 + var monthly = ui.Map.Layer(
     213 + comp_monthly,
     214 + { min: [-25, -20, -25], max: [-10, 0, -10], opacity: 0.8 },
     215 + "Month",
     216 + "Month" == layerSelect.getValue()
     217 + );
     218 + var yearly = ui.Map.Layer(
     219 + comp_annual,
     220 + { min: [-25, -20, -25], max: [-10, 0, -10], opacity: 0.8 },
     221 + "Year",
     222 + "Year" == layerSelect.getValue()
     223 + );
     224 + 
     225 + // Add layers to map
     226 + 
     227 + mapPanel.layers().set(0, daily);
     228 + mapPanel.layers().set(1, monthly);
     229 + mapPanel.layers().set(2, yearly);
     230 +};
     231 + 
     232 +// Create dateSlider to trigger the function Slide function
     233 +var dateSlider = ui
     234 + .DateSlider({
     235 + start: start,
     236 + end: now,
     237 + value: null,
     238 + period: 1,
     239 + onChange: slide,
     240 + style: { height: "0px" },
     241 + })
     242 + .setValue(now);
     243 + 
     244 +// --------------------- Step 7: Create RFI Chart --------------------------------
     245 + 
     246 +// Create panels to hold lon/lat values.
     247 +var lon = ui.Label();
     248 +var lat = ui.Label();
     249 + 
     250 +// Generates a new time series chart of RFI for the given coordinates.
     251 +var generateChart = function (coords) {
     252 + // Update the lon/lat panel with values from the click event.
     253 + lon.setValue("lon: " + coords.lon.toFixed(2));
     254 + lat.setValue("lat: " + coords.lat.toFixed(2));
     255 + 
     256 + // Add a dot for the point clicked on.
     257 + var point = ee.FeatureCollection(ee.Geometry.Point(coords.lon, coords.lat));
     258 + 
     259 + var dot = ui.Map.Layer(
     260 + point.style({ color: "black", fillColor: "#00FFFF", pointSize: 7 }),
     261 + {},
     262 + "clicked location"
     263 + );
     264 + // Add the dot as the second layer, so it shows up on top of the composite.
     265 + mapPanel.layers().set(3, dot);
     266 + 
     267 + // Make a chart from the time series.
     268 + var rfiChart = ui.Chart.image
     269 + .series(vh.select("VH"), point, ee.Reducer.max(), 500)
     270 + .setOptions({
     271 + title:
     272 + "Radio Frequency Interference at (lon:" +
     273 + coords.lon.toFixed(2) +
     274 + ", lat:" +
     275 + coords.lat.toFixed(2) +
     276 + ")",
     277 + vAxis: { title: "VH " },
     278 + lineWidth: 2,
     279 + series: "Area of Interest",
     280 + });
     281 + // Add the chart at a fixed position, so that new charts overwrite older ones.
     282 + inspectorPanel.widgets().set(3, rfiChart);
     283 + var getDate = function (callback) {
     284 + dateSlider.setValue(ee.Date(callback));
     285 + };
     286 + rfiChart.onClick(getDate);
     287 +};
     288 + 
     289 +// --------------------- Step 8: Create "Visit Example Locations" dropdown --------------------------------
     290 + 
     291 +// Define functions triggered on selection of locations from the dropdown
     292 +// These generally ensure that the correct layers are being displayed,
     293 +// Display some information on the location being viewed
     294 + 
     295 +var contact = ui.Panel([
     296 + ui.Label(
     297 + "Please direct queries to @oballinger",
     298 + { "font-size": "9px" },
     299 + "https://twitter.com/oballinger"
     300 + ),
     301 +]);
     302 + 
     303 +var configureExample = function (text, opacity) {
     304 + mapPanel.layers().map(function (layer) {
     305 + layer.setShown(false);
     306 + });
     307 + mapPanel.layers().get(1).setShown(true);
     308 + mapPanel.layers().get(3).setShown(true);
     309 + mapPanel.layers().get(1).setOpacity(opacity);
     310 + 
     311 + var textpanel = ui.Panel(text);
     312 + inspectorPanel.widgets().set(10, textpanel);
     313 + inspectorPanel.widgets().set(11, contact);
     314 +};
     315 + 
     316 +// Dammam (Saudi Arabia) Patriot Missile
     317 +var loc1_function = function () {
     318 + // Draw boxes around the different components of the Patriot Missile identified in Dammam
     319 + var radar = ee.Geometry.Polygon(
     320 + [
     321 + [
     322 + [49.95055676743417, 26.60577361047956],
     323 + [49.95055676743417, 26.605668090179968],
     324 + [49.9506694202128, 26.605668090179968],
     325 + [49.9506694202128, 26.60577361047956],
     326 + ],
     327 + ],
     328 + null,
     329 + false
     330 + ),
     331 + power = ee.Geometry.Polygon(
     332 + [
     333 + [
     334 + [49.95069356009393, 26.6057028639258],
     335 + [49.95069356009393, 26.605602139943276],
     336 + [49.950796825141005, 26.605602139943276],
     337 + [49.950796825141005, 26.6057028639258],
     338 + ],
     339 + ],
     340 + null,
     341 + false
     342 + ),
     343 + control = ee.Geometry.Polygon(
     344 + [
     345 + [
     346 + [49.9507780496779, 26.605594945369706],
     347 + [49.9507780496779, 26.605497818582162],
     348 + [49.95088399693399, 26.605497818582162],
     349 + [49.95088399693399, 26.605594945369706],
     350 + ],
     351 + ],
     352 + null,
     353 + false
     354 + ),
     355 + launchers = ee.Geometry.MultiPolygon(
     356 + [
     357 + [
     358 + [
     359 + [49.949325633496336, 26.60563452803374],
     360 + [49.949325633496336, 26.6054690527739],
     361 + [49.94951338812738, 26.6054690527739],
     362 + [49.94951338812738, 26.60563452803374],
     363 + ],
     364 + ],
     365 + [
     366 + [
     367 + [49.94854242846399, 26.6054582609008],
     368 + [49.94854242846399, 26.60532396195055],
     369 + [49.948700678795866, 26.60532396195055],
     370 + [49.948700678795866, 26.6054582609008],
     371 + ],
     372 + ],
     373 + [
     374 + [
     375 + [49.9505621318522, 26.606960694701094],
     376 + [49.9505621318522, 26.606833592010936],
     377 + [49.950706971139006, 26.606833592010936],
     378 + [49.950706971139006, 26.606960694701094],
     379 + ],
     380 + ],
     381 + [
     382 + [
     383 + [49.9504172925654, 26.60782678197863],
     384 + [49.9504172925654, 26.60769968025089],
     385 + [49.95055140301614, 26.60769968025089],
     386 + [49.95055140301614, 26.60782678197863],
     387 + ],
     388 + ],
     389 + ],
     390 + null,
     391 + false
     392 + );
     393 + 
     394 + var outline = ee
     395 + .Image()
     396 + .byte()
     397 + .paint({
     398 + featureCollection: radar,
     399 + width: 5,
     400 + color: 1,
     401 + })
     402 + .paint({
     403 + featureCollection: power,
     404 + width: 5,
     405 + color: 2,
     406 + })
     407 + .paint({
     408 + featureCollection: control,
     409 + width: 5,
     410 + color: 3,
     411 + })
     412 + .paint({
     413 + featureCollection: launchers,
     414 + width: 5,
     415 + color: 0,
     416 + });
     417 + 
     418 + mapPanel.addLayer(outline, {
     419 + palette: ["black", "red", "green", "blue"],
     420 + min: 0,
     421 + max: 3,
     422 + });
     423 + 
     424 + // Display information on the site
     425 + var lab1 = ui.Label(
     426 + "This is a MIM-104 Patriot PAC-2 missile defense system stationed at an Aramco oil refinery in Dammam, Saudi Arabia. At the center of the system are three vehicles: the AN/MPQ-53 radar (red), the control station (blue), and the power generator truck (green). The black boxes indicate the missile launcher trucks."
     427 + );
     428 + var link = ui.Label(
     429 + "This video provides an overivew of the Patriot missile system.",
     430 + {},
     431 + "https://youtu.be/NG8wF1o6r58?t=29"
     432 + );
     433 + var lab2 = ui.Label(
     434 + "By gradually zooming out and increasing the opacity of the Synthetic Aperture Radar layer using the slider above, it becomes clear that the radar on this missile defense system causing significant interference with the Sentinel-1 satellite."
     435 + );
     436 + var lab3 = ui.Label(
     437 + "The RFI Graph above shows that the radar was first turned on a this location around April 26th, 2021. There is a drop in intereference in July and August, suggesting that it was turned off during this period. The radar comes back online in September, and has been on ever since."
     438 + );
     439 + 
     440 + configureExample([lab1, link, lab2, lab3], 0.1);
     441 +};
     442 + 
     443 +// Dimona Radar Facility
     444 +var loc2_function = function () {
     445 + var lab1 = ui.Label(
     446 + 'Located in Israel\'s Negev Desert, the Dimona Radar Facility is a "top-secret X-band radar staffed by around 120 American technicians".',
     447 + {},
     448 + "http://content.time.com/time/world/article/0,8599,1846749,00.html"
     449 + );
     450 + var lab2 = ui.Label(
     451 + "The radar can monitor the take-off of any aircraft or missile up to 1,500 miles away, which would give Israel an extra 60-70 seconds to react if Iran fired a missile. The radar is so powerful that Israeli officials feared that RFI would impact the accuracy of anti-tank missiles being tested nearby."
     452 + );
     453 + var lab3 = ui.Label(
     454 + "Israel's Negev Nuclear Research Center is located in the same valley, just a few kilometers to the north. The RFI Graph above shows consistent and strong interference since 2017."
     455 + );
     456 + 
     457 + configureExample([lab1, lab2, lab3], 0.8);
     458 +};
     459 + 
     460 +// Rostov Radar
     461 +var loc3_function = function () {
     462 + var lab1 = ui.Label(
     463 + "Rostov-On-Don has seen a significant military buildup and hosts the headquarters of Russia’s 4th Air and Air Defense Forces Command. The dot indictes a facility that is likely the source of the RFI."
     464 + );
     465 + var lab2 = ui.Label(
     466 + 'According to Wikimapia, this facility is operated by FEDERAL STATE UNITARY ENTERPRISE "ROSTOV-ON-DON RESEARCH INSTITUTE OF RADIO COMMUNICATIONS"',
     467 + {},
     468 + "https://www.openstreetmap.org/way/106283207#map=17/47.35430/39.78441"
     469 + );
     470 + var lab3 = ui.Label(
     471 + "Its official registration lists it as a subsidiary of the Federal Security Services of the Russian Federation (FSB). The RFI graph above shows radar activity throughout June and July 2021. You can view the facility likely causing this interference by zooming in to the blue dot and reducing the opacity using the slider."
     472 + );
     473 + 
     474 + configureExample([lab1, lab2, lab3], 0.8);
     475 +};
     476 + 
     477 +// White Sands Missile Range
     478 +var loc4_function = function () {
     479 + var lab1 = ui.Label(
     480 + "The White Sands Missile Range (WSMR) is a U.S. Military base located in New Mexico.",
     481 + {},
     482 + "https://en.wikipedia.org/wiki/White_Sands_Missile_Range"
     483 + );
     484 + var lab2 = ui.Label(
     485 + "Patriot missiles are often tested at Launch Complex 38. The RFI graph shows significant radar activity on December 14th, 2021, and February 23rd, 2020. Smaller signatures are also visible in April and June 2021, as well as at various points since 2017."
     486 + );
     487 + 
     488 + configureExample([lab1, lab2], 0.8);
     489 +};
     490 + 
     491 +// Some pre-set locations of interest that will be loaded into a pulldown menu.
     492 +// Dict contains the coordinates, zoom level, date range, and function to be triggered when navigating to these locations
     493 +var locationDict = {
     494 + "Dammam, Saudi Arabia": {
     495 + lon: 49.949916,
     496 + lat: 26.606379,
     497 + zoom: 19,
     498 + date: "2022-01-01",
     499 + func: loc1_function,
     500 + },
     501 + "Dimona Radar Facility, Israel": {
     502 + lon: 35.0948799,
     503 + lat: 30.9685089,
     504 + zoom: 11,
     505 + date: "2019-02-19",
     506 + func: loc2_function,
     507 + },
     508 + "Rostov-on-Don, Russia": {
     509 + lon: 39.783387,
     510 + lat: 47.354445,
     511 + zoom: 11,
     512 + date: "2021-07-22",
     513 + func: loc3_function,
     514 + },
     515 + "White Sands Missile Range, USA": {
     516 + lon: -106.3122,
     517 + lat: 31.9735,
     518 + zoom: 10,
     519 + date: "2021-12-14",
     520 + func: loc4_function,
     521 + },
     522 +};
     523 + 
     524 +// Create the location pulldown.
     525 +var locations = Object.keys(locationDict);
     526 +var locationSelect = ui
     527 + .Select({
     528 + items: locations,
     529 + onChange: function (value) {
     530 + var location = locationDict[value];
     531 + 
     532 + mapPanel.setCenter(location.lon, location.lat, location.zoom);
     533 + 
     534 + generateChart({
     535 + lon: location.lon,
     536 + lat: location.lat,
     537 + });
     538 + 
     539 + dateSlider.setValue(location.date);
     540 + location.func();
     541 + },
     542 + })
     543 + .setPlaceholder("Choose a Location");
     544 + 
     545 +var locationPanel = ui.Panel([
     546 + ui.Label("Visit Example Locations", { "font-size": "24px" }),
     547 + locationSelect,
     548 +]);
     549 + 
     550 +// --------------------- Step 9: Map setup --------------------------------
     551 + 
     552 +// Register a callback on the default map to be invoked when the map is clicked.
     553 +mapPanel.onClick(generateChart);
     554 + 
     555 +// Configure the map.
     556 +mapPanel.setOptions("Satellite");
     557 +mapPanel.style().set("cursor", "crosshair");
     558 + 
     559 +// Initialize with a test point.
     560 +var initialPoint = ee.Geometry.Point(49.950656, 26.605644);
     561 +mapPanel.centerObject(initialPoint, 11);
     562 + 
     563 +// Add all of the modules created above to the User Interface Panel
     564 +inspectorPanel.add(intro);
     565 +inspectorPanel.add(dateSlider);
     566 +inspectorPanel.add(ui.Panel([lon, lat], ui.Panel.Layout.flow("horizontal")));
     567 +inspectorPanel.add(ui.Label("placeholder"));
     568 +inspectorPanel.add(
     569 + ui.Label(
     570 + "Click on any point in the graph above to display imagery from that date"
     571 + )
     572 +);
     573 + 
     574 +inspectorPanel.add(ui.Label("View Different Layers", { "font-size": "24px" }));
     575 +inspectorPanel.add(
     576 + ui.Panel([image_info, date], ui.Panel.Layout.flow("horizontal"))
     577 +);
     578 +inspectorPanel.add(
     579 + ui.Label(
     580 + "Use the dropdown below to switch between daily, monthly, and annually aggregated imagery. Annual imagery is useful for monitoring large areas over time for signs of radar activity. When a radar is spotted, monthly and daily imagery can be used for a more detailed investigation."
     581 + )
     582 +);
     583 +inspectorPanel.add(viewPanel);
     584 +inspectorPanel.add(locationPanel);
     585 +inspectorPanel.widgets().set(11, contact);
     586 + 
     587 +// --------------------- Step 10: Initialize --------------------------------
     588 + 
     589 +// Replace the root with a SplitPanel that contains the inspector and map.
     590 +ui.root.clear();
     591 +ui.root.add(ui.SplitPanel(inspectorPanel, mapPanel));
     592 + 
     593 +generateChart({
     594 + lon: initialPoint.coordinates().get(0).getInfo(),
     595 + lat: initialPoint.coordinates().get(1).getInfo(),
     596 +});
     597 + 
     598 +// Optional: add country outlines
     599 +// mapPanel.layers().set(5, country_layer)
     600 + 
     601 +// Add imagery aggregated by month by default.
     602 +mapPanel.layers().get(1).setShown(true);
     603 + 
Please wait...
Page is in error, reload to recover