Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions Penn-night-safety-dashboard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Penn Nighttime Safety Dashboard
A Web-based Safety Visualization Tool for Penn Students

This project is a nighttime safety dashboard designed to help Penn students navigate campus more safely.
It integrates crime data, building safety attributes, nighttime popularity levels, lighting levels, and routing functions.

Features:
1. Interactive Map
Displays campus buildings with: Perceived safety level (1–5), Lighting level, Nighttime popularity, Place category (Academic / Residential / Public / Community).

2. Crime Heatmap
Toggleable hotspot visualization using Penn’s crime incident dataset.

3. Filters
You can filter buildings by: Category, Minimum safety level, Minimum popularity level, Minimum lighting level

4. Search
Search for a building by name. The map pans and opens pop-up info automatically.

5. Navigation: Shortest & Safest Path
Select a start and destination from the dropdown menus.
Two routing options: Shortest route (distance-based), Safest route (crime-weighted road network)

Routes based on Street Centerline road geometry. Crime influence weighted using a Gaussian-based decay model.

Data Sources: OpenDataPhilly(Crime.geojson, Street_Centerline.geojson)
All datasets are projected in WGS84 (EPSG:4326).

Author: Hazel Sun
Master of City Planning, University of Pennsylvania
Course: MUSA Dashboard Project
175 changes: 175 additions & 0 deletions Penn-night-safety-dashboard/css/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
html, body {
height: 100%;
margin: 0;
padding: 0;
font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
}

header {
height: 80px; /* 👈 固定更短的高度 */
display: flex;
align-items: center;
justify-content: center;

background-color: #002855;
color: white;

font-size: 1.2rem; /* 👈 再小一点 */
font-weight: 600;
letter-spacing: 0.3px;

margin: 0;
padding: 0; /* 👈 移除 padding */
}



/* main layout */
main {
display: flex;
height: calc(100vh - 40px); /* header 高度 */
}

/* sidebar */
.sidebar {
width: 280px;
padding: 20px;
background: #f8f9fa;
border-right: 1px solid #ddd;
overflow-y: auto;
}

/* map */
#map {
flex: 1;
height: 100%;
width: 100%;
}

.legend {
background: white;
padding: 12px;
border-radius: 6px;
position: fixed; /* ← 改成固定位置 */
bottom: 20px; /* ← 页面底部 */
right: 20px; /* ← 页面右侧 */
z-index: 9999; /* ← 确保永远在最上层 */
box-shadow: 0 0 10px rgba(0,0,0,0.25);
}



.legend i {
width: 14px;
height: 14px;
display: inline-block;
margin-right: 6px;
border-radius: 3px;
}

/* search block */
.search-block {
margin-top: 20px; /* 和 filter 区分 */
}

.search-block h3 {
margin: 0 0 8px 0;
font-size: 1rem;
}

#place-search {
box-sizing: border-box;
width: 100%;
padding: 6px 8px;
font-size: 0.9rem;
border: 1px solid #ccc;
border-radius: 4px;
}

#search-button {
margin-top: 8px;
padding: 6px 12px;
font-size: 0.9rem;
border: none;
border-radius: 4px;
background-color: #002855;
color: white;
cursor: pointer;
}

#search-button:hover {
background-color: #01408a;
}
.slider-block {
margin-top: 24px;
font-size: 0.9rem;
}

#safety-min,
#pop-min,
#light-min {
width: 100%;
}

.slider-label {
margin-top: 4px;
color: #555;
font-size: 0.85rem;
}
.place-count {
margin: 0 0 0.75rem 0;
font-size: 0.85rem;
color: #555;
}

.nav-block {
margin-bottom: 16px;
font-size: 0.9rem;
}

.nav-block h2 {
font-size: 1.1rem;
margin: 0 0 8px 0;
}

.nav-block label {
display: block;
margin-top: 4px;
margin-bottom: 2px;
}

.nav-block select {
width: 100%;
padding: 4px 6px;
font-size: 0.9rem;
margin-bottom: 6px;
border-radius: 4px;
border: 1px solid #ccc;
box-sizing: border-box;
}

.nav-block button {
display: block;
width: 100%;
margin-top: 6px;
padding: 6px 8px;
font-size: 0.9rem;
border-radius: 4px;
border: none;
cursor: pointer;
background-color: #002855;
color: #fff;
}

.nav-block button.secondary {
background-color: #e0e0e0;
color: #333;
}

.nav-block button:hover:not(.secondary) {
background-color: #01408a;
}

.nav-block button.secondary:hover {
background-color: #cfcfcf;
}
1 change: 1 addition & 0 deletions Penn-night-safety-dashboard/data/Crime.geojson

Large diffs are not rendered by default.

41,266 changes: 41,266 additions & 0 deletions Penn-night-safety-dashboard/data/Street_Centerline.geojson

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Penn-night-safety-dashboard/data/UpennBuilding.geojson

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Penn-night-safety-dashboard/data/UpennBuilding.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"displayFieldName":"","fieldAliases":{"OBJECTID":"OBJECTID","NoteType":"NoteType","Name":"Name","created_user":"created_user","created_date":"created_date","last_edited_user":"last_edited_user","last_edited_date":"last_edited_date","Category":"Category","Popularity_Night":"Popularity_Night","LightingLevel":"LightingLevel","PerceivedSafety":"PerceivedSafety","ReportedIncidents":"ReportedIncidents"},"geometryType":"esriGeometryPoint","spatialReference":{"wkid":102100,"latestWkid":3857},"fields":[{"name":"OBJECTID","type":"esriFieldTypeOID","alias":"OBJECTID"},{"name":"NoteType","type":"esriFieldTypeSmallInteger","alias":"NoteType"},{"name":"Name","type":"esriFieldTypeString","alias":"Name","length":255},{"name":"created_user","type":"esriFieldTypeString","alias":"created_user","length":255},{"name":"created_date","type":"esriFieldTypeDate","alias":"created_date","length":8},{"name":"last_edited_user","type":"esriFieldTypeString","alias":"last_edited_user","length":255},{"name":"last_edited_date","type":"esriFieldTypeDate","alias":"last_edited_date","length":8},{"name":"Category","type":"esriFieldTypeString","alias":"Category","length":255},{"name":"Popularity_Night","type":"esriFieldTypeSmallInteger","alias":"Popularity_Night"},{"name":"LightingLevel","type":"esriFieldTypeSmallInteger","alias":"LightingLevel"},{"name":"PerceivedSafety","type":"esriFieldTypeSmallInteger","alias":"PerceivedSafety"},{"name":"ReportedIncidents","type":"esriFieldTypeSmallInteger","alias":"ReportedIncidents"}],"features":[{"attributes":{"OBJECTID":1,"NoteType":null,"Name":"Meyerson Hall","created_user":null,"created_date":-2209161600000,"last_edited_user":null,"last_edited_date":-2209161600000,"Category":"Academic","Popularity_Night":4,"LightingLevel":2,"PerceivedSafety":4,"ReportedIncidents":1},"geometry":{"x":-8370412.4213999994,"y":4859002.8859999999}}]}
117 changes: 117 additions & 0 deletions Penn-night-safety-dashboard/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Penn Night Safety Dashboard</title>

<!-- Leaflet -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<link rel="stylesheet" href="css/styles.css" />
</head>

<body>
<header>
<h1>Penn Students' Nighttime Safety Map</h1>
</header>

<main>


<div class="sidebar">

<!-- Crime Heatmap Toggle -->
<div class="crime-toggle-block">
<label>
<input type="checkbox" id="crime-toggle" checked>
Show Crime Heatmap
</label>
</div>

<hr>

<!-- Navigation -->
<div class="nav-block">
<h2>Navigation</h2>

<label for="nav-start">Start</label>
<select id="nav-start">
<option value="">Select a place…</option>
</select>

<label for="nav-end" style="margin-top:8px;">Destination</label>
<select id="nav-end">
<option value="">Select a place…</option>
</select>

<button id="btn-shortest-route">Show shortest route</button>
<button id="btn-safest-route">Show safest route</button>
<button id="btn-clear-route" class="secondary">Clear route</button>
</div>

<hr>

<!-- Category filters -->
<h2>Filter by place type</h2>
<p id="place-count" class="place-count"></p>

<label><input type="checkbox" name="category-filter" value="Academic" checked> Academic</label><br>
<label><input type="checkbox" name="category-filter" value="Residential" checked> Residential</label><br>
<label><input type="checkbox" name="category-filter" value="Public" checked> Public</label><br>
<label><input type="checkbox" name="category-filter" value="Community Center" checked> Community Center</label><br>

<!-- Search -->
<div class="search-block">
<h3>Search by place name</h3>
<input type="text" id="place-search" placeholder="e.g. Van Pelt Library" />
<button id="search-button">Search</button>
</div>

<!-- Safety slider -->
<div class="slider-block">
<h3>Filter by safety level</h3>
<label for="safety-min">Minimum perceived safety</label>
<input type="range" id="safety-min" min="1" max="5" step="1" value="1"/>
<div class="slider-label">Showing places with safety ≥ <span id="safety-min-value">1+</span></div>
</div>

<!-- Popularity slider -->
<div class="slider-block">
<h3>Filter by nighttime popularity</h3>
<label for="pop-min">Minimum popularity at night</label>
<input type="range" id="pop-min" min="0" max="5" step="1" value="0"/>
<div class="slider-label">Showing places with popularity ≥ <span id="pop-min-value">0+</span></div>
</div>

<!-- Lighting slider -->
<div class="slider-block">
<h3>Filter by lighting level</h3>
<label for="light-min">Minimum lighting level</label>
<input type="range" id="light-min" min="0" max="5" step="1" value="0"/>
<div class="slider-label">Showing places with lighting ≥ <span id="light-min-value">0+</span></div>
</div>

</div> <!-- sidebar END -->


<div id="map"></div>

<!-- Legend -->
<div id="legend" class="legend">
<h4>Safety Level</h4>
<i style="background:#169425"></i> High (4–5)<br>
<i style="background:#f3d200"></i> Medium (3)<br>
<i style="background:#FF851B"></i> Low (2)<br>
<i style="background:#bd150c"></i> Very Low (1)<br>
</div>

</main>

<!-- Leaflet JS -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet.heat/dist/leaflet-heat.js"></script>
<script type="module" src="js/main.js"></script>

</body>
</html>

Loading