Skip to content

Commit 7af4158

Browse files
committed
refactor: update terminology from "optimal" to "model-suggested" for shelter locations across documentation and codebase
1 parent 25f5fb3 commit 7af4158

7 files changed

Lines changed: 44 additions & 44 deletions

File tree

CITATION.cff

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ date-released: 2026-07-17
77
abstract: >-
88
A real-time, browser-based interactive map for analyzing bomb shelter
99
accessibility for Bedouin communities in the Eastern Negev. The tool
10-
identifies underserved areas and proposes optimal new shelter locations
10+
identifies underserved areas and proposes model-suggested new shelter locations
1111
using precomputed DBSCAN and K-means spatial clustering, with a static
1212
web frontend (deck.gl) and offline Python analysis scripts.
1313
authors:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Negev Shelter Access Analysis
22

3-
Interactive map for analyzing bomb shelter accessibility for Bedouin communities in the Eastern Negev. The app identifies underserved areas and proposes optimal new shelter locations using precomputed DBSCAN + K-means clustering. Covered in [Yediot Ahronot / Ynet](https://www.ynet.co.il/architecture/article/ry9tp9gtxe).
3+
Interactive map for analyzing bomb shelter accessibility for Bedouin communities in the Eastern Negev. The app identifies underserved areas and proposes model-suggested new shelter locations using precomputed DBSCAN + K-means clustering. Covered in [Yediot Ahronot / Ynet](https://www.ynet.co.il/architecture/article/ry9tp9gtxe).
44

55
**Live demo:** [negevurbanresearch.github.io/shelter_access](https://negevurbanresearch.github.io/shelter_access/)
66

index.html

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -329,15 +329,15 @@ <h2>Data Layers</h2>
329329
src="data/proposed.svg"
330330
width="16"
331331
height="16"
332-
alt="Optimal shelter icon"
332+
alt="Model-suggested shelter icon"
333333
/>
334334
</div>
335335
<label class="toggle-switch">
336336
<input type="checkbox" id="optimalSheltersLayer" checked />
337337
<span class="toggle-slider"></span>
338338
</label>
339339
<div class="layer-info">
340-
<span class="layer-label">Optimal Shelters</span>
340+
<span class="layer-label">Model-Suggested Shelters</span>
341341
<span class="layer-description"
342342
>Green plus icons • Algorithm sites</span
343343
>
@@ -445,7 +445,7 @@ <h1 data-i18n="modal.title">The Right to Shelter</h1>
445445
<p>
446446
This analysis evaluates bomb shelter accessibility for
447447
Bedouin communities in the Eastern Negev, identifying
448-
underserved areas and proposing optimal new shelter
448+
underserved areas and proposing model-suggested new shelter
449449
locations using spatial data analysis and machine learning
450450
methods.
451451
</p>
@@ -481,7 +481,7 @@ <h3>Background & Context</h3>
481481
formal and informal efforts have aimed to construct bomb
482482
shelters for the widely dispersed Bedouin communities
483483
living in unrecognized villages with a dangerous lack of
484-
shelter. However, identifying optimal locations has been
484+
shelter. However, identifying suitable locations has been
485485
challenging due to the absence of formal data on
486486
population distribution and density, as well as the
487487
condition and location of local roads.
@@ -501,7 +501,7 @@ <h3>What This Analysis Shows</h3>
501501
<p>
502502
This tool analyzes bomb shelter accessibility within the
503503
Negev's informal Bedouin settlements and proposes
504-
optimal locations for new shelters to broaden coverage.
504+
model-suggested locations for new shelters to broaden coverage.
505505
On the accessibility grid,
506506
<strong style="color: #14b814">green areas</strong>
507507
signify neighborhoods with adequate shelter access under

js/app.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ class ShelterAccessApp {
198198
return precomputedCoverage;
199199
}
200200

201-
// Fallback to calculation for optimal shelters or missing data
201+
// Fallback to calculation for model-suggested shelters or missing data
202202
return this.calculateShelterCoverage(shelter);
203203
}
204204

@@ -242,7 +242,7 @@ class ShelterAccessApp {
242242
await this.initializeMap();
243243
this.updateAttribution();
244244

245-
// Initial load of optimal locations and coverage analysis
245+
// Initial load of model-suggested locations and coverage analysis
246246
await this.updateOptimalLocations();
247247

248248

@@ -452,7 +452,7 @@ class ShelterAccessApp {
452452
// Update visualization to refresh the heatmap
453453
this.updateVisualization();
454454
} else {
455-
// Otherwise just update optimal locations
455+
// Otherwise just update model-suggested locations
456456
await this.updateOptimalLocations();
457457
}
458458
}
@@ -511,7 +511,7 @@ class ShelterAccessApp {
511511
await this.loadAccessibilityData();
512512
}
513513
} else {
514-
// Update optimal locations when disabling heatmap
514+
// Update model-suggested locations when disabling heatmap
515515
await this.updateOptimalLocations();
516516
}
517517

@@ -1232,7 +1232,7 @@ class ShelterAccessApp {
12321232
}
12331233
}
12341234

1235-
// === OPTIMAL ADDED SHELTERS (Green Squares) ===
1235+
// === MODEL-SUGGESTED ADDED SHELTERS (Green Squares) ===
12361236
if (this.layerVisibility.optimalShelters && this.proposedShelters.length > 0 && !this.layerVisibility.accessibilityHeatmap) {
12371237
// Added shelter squares with quality-based coloring
12381238
layers.push(new deck.IconLayer({
@@ -1308,7 +1308,7 @@ class ShelterAccessApp {
13081308
legendItems.push({
13091309
type: 'svg-icon',
13101310
className: 'optimal-shelter',
1311-
label: 'Optimal Shelters',
1311+
label: 'Model-Suggested Shelters',
13121312
iconSrc: 'data/proposed.svg',
13131313
description: 'Algorithm-generated sites'
13141314
});
@@ -1751,15 +1751,15 @@ class ShelterAccessApp {
17511751
}
17521752

17531753
/**
1754-
* Update optimal locations in real-time
1754+
* Update model-suggested locations in real-time
17551755
*/
17561756
async updateOptimalLocations() {
17571757
if (this.isAnalyzing) return;
17581758

17591759
try {
17601760
this.isAnalyzing = true;
17611761

1762-
// Load optimal locations from precomputed data
1762+
// Load model-suggested locations from precomputed data
17631763
const optimalLocations = await this.spatialAnalyzer.getOptimalLocations(this.numNewShelters);
17641764

17651765
// Store directly - they're already in the right format
@@ -1772,7 +1772,7 @@ class ShelterAccessApp {
17721772
this.updateCoverageAnalysis();
17731773

17741774
} catch (error) {
1775-
console.error('Loading optimal locations failed:', error);
1775+
console.error('Loading model-suggested locations failed:', error);
17761776
} finally {
17771777
this.isAnalyzing = false;
17781778
}
@@ -1792,7 +1792,7 @@ class ShelterAccessApp {
17921792
const stats = data.statistics;
17931793
const newSheltersSelected = this.proposedShelters.length;
17941794

1795-
// Calculate coverage from selected optimal shelters
1795+
// Calculate coverage from selected model-suggested shelters
17961796
let newBuildingsCovered = 0;
17971797
if (newSheltersSelected > 0) {
17981798
newBuildingsCovered = this.proposedShelters.reduce((sum, shelter) => sum + (shelter.buildings_covered || 0), 0);

js/spatial-analysis-simple.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Simple Spatial Analysis Module - Loads Precomputed Optimal Shelter Locations
2+
* Simple Spatial Analysis Module - Loads Precomputed Model-Suggested Shelter Locations
33
* Uses precalculated data from shelter_optimizer.py DBSCAN + Greedy algorithm
44
*/
55

@@ -41,7 +41,7 @@ class SimpleSpatialAnalyzer {
4141
// Load pre-filtered polygon layers
4242
await this.loadPolygonLayers();
4343

44-
// Load default optimal data
44+
// Load default model-suggested data
4545
await this.loadOptimalData(this.coverageRadius);
4646

4747
return true;
@@ -180,24 +180,24 @@ class SimpleSpatialAnalyzer {
180180
}
181181

182182
/**
183-
* Load precomputed optimal shelter data
183+
* Load precomputed model-suggested shelter data
184184
*/
185185
async loadOptimalData(radius) {
186-
// Only support 'optimal_shelters' scenario
186+
// Only support the model-suggested shelter scenario
187187
const cacheKey = `optimal_shelters_${radius}m`;
188188
if (this.optimalData.has(cacheKey)) {
189189
return this.optimalData.get(cacheKey);
190190
}
191191
try {
192-
console.log(`Loading optimal data: ${cacheKey}...`);
192+
console.log(`Loading model-suggested data: ${cacheKey}...`);
193193
const response = await fetch(`data/optimal_locations/${cacheKey}.json`);
194194
if (!response.ok) {
195195
throw new Error(`Failed to load: ${response.status}`);
196196
}
197197
const data = await response.json();
198198
this.optimalData.set(cacheKey, data);
199199
console.log(
200-
`✓ Loaded ${data.optimal_locations.length} optimal locations for ${cacheKey}`
200+
`✓ Loaded ${data.optimal_locations.length} model-suggested locations for ${cacheKey}`
201201
);
202202
return data;
203203
} catch (error) {
@@ -226,7 +226,7 @@ class SimpleSpatialAnalyzer {
226226
}
227227

228228
/**
229-
* Get top N optimal shelter locations
229+
* Get top N model-suggested shelter locations
230230
*/
231231
async getOptimalLocations(numShelters) {
232232
const data = await this.loadOptimalData(this.coverageRadius);
@@ -239,7 +239,7 @@ class SimpleSpatialAnalyzer {
239239
}
240240

241241
/**
242-
* Get requested shelter evaluation with specific pairing to optimal locations
242+
* Get requested shelter evaluation with specific pairing to model-suggested locations
243243
*/
244244
getRequestedShelterEvaluation(numNewShelters = 0) {
245245
const cacheKey = `optimal_shelters_${this.coverageRadius}m`;
@@ -271,10 +271,10 @@ class SimpleSpatialAnalyzer {
271271
(a, b) => (a.buildings_covered || 0) * 7 - (b.buildings_covered || 0) * 7
272272
);
273273

274-
// Get the top N optimal locations we're actually building
274+
// Get the top N model-suggested locations we're actually building
275275
const optimalLocations = data.optimal_locations.slice(0, numNewShelters);
276276

277-
// Pair worst requested with best optimal, but only up to the number we're building
277+
// Pair worst requested with best model suggestion, up to the number being built
278278
const pairedShelters = [];
279279
for (let i = 0; i < maxReplacements; i++) {
280280
const requested = sortedRequested[i];
@@ -292,7 +292,7 @@ class SimpleSpatialAnalyzer {
292292
requestedCoverage: requestedCoverage,
293293
optimalCoverage: optimalCoverage,
294294
requestedRank: i + 1, // Rank among worst requested (1 = worst)
295-
optimalRank: i + 1, // Rank among selected optimal (1 = best)
295+
optimalRank: i + 1, // Rank among selected model suggestions (1 = best)
296296
});
297297
}
298298
}
@@ -344,7 +344,7 @@ class SimpleSpatialAnalyzer {
344344
0
345345
);
346346

347-
// Existing coverage (total - new from all optimal locations)
347+
// Existing coverage (total - new from all model-suggested locations)
348348
const existingCoverage =
349349
(stats.total_people_covered || 0) - (stats.new_people_covered || 0);
350350
const totalCoverage = existingCoverage + newPeopleCovered;

scripts/generate_shelter_statistics.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ def create_coverage_analysis(theme):
252252

253253
ax.text(radii[-1] + 8, existing_coverage[-1], 'Existing',
254254
va='center', fontsize=12, color=existing_line_color)
255-
ax.text(radii[-1] + 8, total_coverage[-1], '+500 Optimal',
255+
ax.text(radii[-1] + 8, total_coverage[-1], '+500 Model-Suggested',
256256
va='center', fontsize=12, color=theme['optimal_color'])
257257

258258
ax.set_xlabel('Coverage Radius (m)', fontsize=14)
@@ -297,7 +297,7 @@ def create_coverage_analysis(theme):
297297

298298
ax.text(x[0] - width/2, buildings_existing[0] + 300, 'Existing',
299299
ha='center', fontsize=10, color='#000000')
300-
ax.text(x[0] + width/2, buildings_total[0] + 300, '+500 Optimal',
300+
ax.text(x[0] + width/2, buildings_total[0] + 300, '+500 Model-Suggested',
301301
ha='center', fontsize=10, color='#000000')
302302

303303
ax.text(bars1[-1].get_x() + bars1[-1].get_width()/2., bars1[-1].get_height() + 100,
@@ -354,7 +354,7 @@ def calculate_incremental_coverage(building_coords, existing_shelters, optimal_s
354354
initial_coverage = np.sum(covered_mask) / total_buildings * 100
355355
coverage_percentages = [initial_coverage]
356356

357-
# Add optimal shelters one by one
357+
# Add model-suggested shelters one by one
358358
for shelter in optimal_shelters:
359359
# Vectorized distance calculation for uncovered buildings only
360360
uncovered_indices = np.where(~covered_mask)[0]
@@ -477,10 +477,10 @@ def load_accessibility_data():
477477
'num_shelters': len(optimal_shelters)
478478
}
479479

480-
print(f" {radius}m: {len(optimal_shelters)} optimal shelters, "
480+
print(f" {radius}m: {len(optimal_shelters)} model-suggested shelters, "
481481
f"final coverage: {coverage_progression[-1]:.1f}%")
482482
except FileNotFoundError:
483-
print(f"Optimal locations data for {radius}m not found")
483+
print(f"Model-suggested locations data for {radius}m not found")
484484
continue
485485

486486
return radius_data, coverage_radii
@@ -491,7 +491,7 @@ def print_coverage_statistics():
491491
coverage_radii = [100, 150, 200, 250, 300]
492492

493493
print("\n=== SHELTER COVERAGE STATISTICS ===\n")
494-
print(f"{'Radius':<10} {'Existing Coverage':<20} {'With +500 Optimal':<20} {'Improvement':<15}")
494+
print(f"{'Radius':<10} {'Existing Coverage':<20} {'With +500 Model-Suggested':<26} {'Improvement':<15}")
495495
print("-" * 65)
496496

497497
total_buildings = None
@@ -532,7 +532,7 @@ def load_buildings_per_shelter_data():
532532
print("Shelter coverage precomputed data not found, skipping comparison chart")
533533
return None, None, None
534534

535-
# Load building coordinates for recalculating optimal coverage
535+
# Load building coordinates for recalculating model-suggested coverage
536536
try:
537537
with open('data/buildings.geojson', 'r') as f:
538538
buildings_data = json.load(f)
@@ -573,11 +573,11 @@ def load_buildings_per_shelter_data():
573573
optimal_avg.append(avg_coverage)
574574
radii.append(radius)
575575
except FileNotFoundError:
576-
print(f"Optimal locations data for {radius}m not found")
576+
print(f"Model-suggested locations data for {radius}m not found")
577577
continue
578578

579579
if not radii:
580-
print("No optimal location data found, skipping comparison chart")
580+
print("No model-suggested location data found, skipping comparison chart")
581581
return None, None, None
582582

583583
existing_avg = [existing_stats[f'{r}m']['average_buildings_per_shelter'] for r in radii]
@@ -586,7 +586,7 @@ def load_buildings_per_shelter_data():
586586

587587

588588
def create_buildings_per_shelter_comparison(theme, radii, existing_avg, optimal_avg):
589-
"""Compare buildings per shelter: existing vs optimal locations"""
589+
"""Compare buildings per shelter: existing vs model-suggested locations"""
590590
if radii is None:
591591
return
592592

@@ -601,14 +601,14 @@ def create_buildings_per_shelter_comparison(theme, radii, existing_avg, optimal_
601601

602602
ax.set_xlabel('Coverage Radius (meters)')
603603
ax.set_ylabel('Average Buildings per Shelter')
604-
ax.set_title('Shelter Efficiency: Existing vs Optimal Locations', pad=15)
604+
ax.set_title('Shelter Efficiency: Existing vs Model-Suggested Locations', pad=15)
605605
ax.set_xticks(x)
606606
ax.set_xticklabels([f'{r}m' for r in radii])
607607

608608
# Direct labeling on first bar group
609609
ax.text(x[0] - width/2, existing_avg[0] + 0.8, 'Existing',
610610
ha='center', fontsize=8, color=theme['existing_color'])
611-
ax.text(x[0] + width/2, optimal_avg[0] + 0.8, '+500 Optimal',
611+
ax.text(x[0] + width/2, optimal_avg[0] + 0.8, '+500 Model-Suggested',
612612
ha='center', fontsize=8, color=theme['optimal_color'])
613613

614614
setup_tufte_axis(ax)

scripts/shelter_optimizer_ensemble.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python3
22
"""
33
Enhanced DBSCAN + K-means Shelter Optimizer
4-
Finds optimal shelter locations using two complementary methods:
4+
Finds model-optimized shelter locations using two complementary methods:
55
1. DBSCAN variants (24 configurations) to find density-based clusters
66
2. K-means clustering (k=750, 1500) for systematic space coverage
77
Then uses advanced selection strategies.
@@ -26,7 +26,7 @@ def __init__(self):
2626
self.N_RUNS_PER_RADIUS = 1 # Single run since DBSCAN is deterministic
2727
self.N_KMEANS_SEEDS = 2 # Multiple K-means random seeds
2828

29-
# DBSCAN parameters to test (eps should be <= coverage radius for optimal results)
29+
# DBSCAN parameters to test (eps should be <= coverage radius for model-optimized results)
3030
self.DBSCAN_EPS_MULTIPLIERS = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] # 10 multipliers from 0.1 to 1.0
3131
self.DBSCAN_MIN_SAMPLES = [10] # Single min_samples parameter
3232

@@ -927,7 +927,7 @@ def run_full_optimization(self, buildings_file, shelters_file, output_dir='data/
927927
print(f"Target: {self.TARGET_SHELTERS} shelters per radius")
928928
print(f"Radii: {self.RADII_TO_TEST} meters")
929929
print(f"Methods: Original DBSCAN + Enhanced DBSCAN (24 configs) + K-means (k=750,1500 × {self.N_KMEANS_SEEDS} seeds)")
930-
print(f"Strategy: Generate diverse candidates, then optimal non-overlapping selection")
930+
print(f"Strategy: Generate diverse candidates, then model-optimized non-overlapping selection")
931931
print(f"Multithreading: {'Enabled' if self.USE_MULTITHREADING else 'Disabled'} ({self.MAX_WORKERS} workers)" if self.USE_MULTITHREADING else "Multithreading: Disabled")
932932
print("=" * 70)
933933
print("📁 Loading data...")

0 commit comments

Comments
 (0)