File size: 15,130 Bytes
7dac171 a5f74e7 2801310 a5f74e7 ff95c12 a5f74e7 6e3b9a5 a5f74e7 f84300f a5f74e7 f84300f a5f74e7 f84300f a5f74e7 f84300f a5f74e7 f84300f a5f74e7 a648083 a5f74e7 6e3b9a5 5f47308 a5f74e7 38f3aef a5f74e7 38f3aef a5f74e7 38f3aef a5f74e7 5c72c6f a5f74e7 7dac171 a5f74e7 5c72c6f | 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 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 | import streamlit as st
import pandas as pd
import altair as alt
import json
from pathlib import Path
#local test (put in terminal... if u dont knwo how to do that oh well)
#python -m streamlit run src/streamlit_app.py
root = Path(__file__).resolve().parent.parent
tiles_path = root / "data" / "tiles"
#title and authors and setup
st.set_page_config(page_title= "Mass Surveillance Across America", layout= "wide")
chartwidth= 950
mapheight= 560
chartheight= 420
st.title("Mass Surveillance Across America")
st.subheader("Visualizing Growing Camera Prevalence in the United States")
#fix ur names im lazy its 4am
st.markdown("""Authors: Thomas Southey, Sam, Tergel, Esther Valentin""")
#intro
st.markdown("Across the United States, thousands of cameras are embedded into the infrasturcture of our everyday lives, mounted in our neighborhoods, street poles, parking lots, and highways there are roughly 70,000 cameras within this dataset alone. This project sets out to map and visualize that network, drawing on a public dataset compiled through a GitHub project called FLOCK, which aggregates camera location data from multiple open-source contributors, including OpenStreetMap, the Electronic Frontier Foundation's Atlas of Surveillance, and community-sourced Flock Safety camera locations. The original dataset included international camera records, but for the purposes of this article and its visualizations, we have narrowed our focus to camera records within the United States. ")
# Intro& Purpose
# -Explain what the FLOCK dataset is, where it comes from, and why it matters.
# -Intro to the idea of surveillance infrastructure and the ecosystem around it (manufacturers, operators, etc. and how they sell that data to govt)
# -Explain why mapping cameras can changes public perception by making them aware of this invasive surveillance.
# -Basically that seeing a camera here and there seems miniscule, but when u zoom out its fuckin crazy
# -tdlr this project looks at the sheer scale rather than identifying individuals
st.divider()
#load data
#mostly just stole this from what i already sent yall b4 and in my invdiv project
#if u dont like it go ham lol its probably a little wrong or exess, if ur feeling pythonic
@st.cache_data
def loadtiles():
files= list(tiles_path.rglob("*.json"))
rows= []
for file in files:
if file.name== "index.json":
continue
with open(file, "r", encoding= "utf-8") as f:
tile= json.load(f)
if "features" not in tile:
continue
for feature in tile["features"]:
props= feature.get("properties", {})
geom= feature.get("geometry", {})
if geom.get("type")!= "Point":
continue
coords= geom.get("coordinates", [None, None])
if coords[0] is None or coords[1] is None:
continue
rows.append({
"lon": coords[0],
"lat": coords[1],
"operator": props.get("operator", "unknown"),
"name": props.get("name", "unknown"),
"manufacturer": props.get("manufacturer", "unknown"),
"type": props.get("camera:type", props.get("type", props.get("surveillance:type", "unknown"))),
"direction": props.get("camera:direction", "unknown"),
"website": props.get("contact:webcam", "unknown")})
return pd.DataFrame(rows)
df = loadtiles()
#clean data
df["operator"]= df["operator"].fillna("unknown").replace("", "unknown")
df["manufacturer"]= df["manufacturer"].fillna("unknown").replace("", "unknown")
df["name"] = df["name"].fillna("unknown").replace("", "unknown")
df["type"] = df["type"].fillna("unknown").replace("", "unknown")
#lock to america only for the map viz. explained later better.
df= df[(df["lon"] >= -130) & (df["lon"] <= -60) & (df["lat"] >= 20) & (df["lat"] <= 55)]
#ai helped me fix the dupe names it was pmo, but i manually did a few weird ones
#i specifically just printed all unique company names and asked
#it to make a dictionary to overwrite the bazillion unique name
#variations with the most reasonable company name.
#This didn't help as much as i wanted with the unknowns...i would appreciate any help with that.
companyfix = {
#mine
"Q108485435": "Unknown",
"wikidata=Q108485435": "Unknown",
"wikidata=q108485435": "Unknown",
"wikidata=Q135925643": "Unknown",
#ai helped me with these
"?": "Unknown",
"UNKNOWN": "Unknown",
"unknown": "Unknown",
"Unknown": "Unknown",
"unkn": "Unknown",
"Unkn": "Unknown",
"Unkwn": "Unknown",
"FLOCK SAFETY": "Flock Safety",
"FLOCK Safety": "Flock Safety",
"Floc Safety": "Flock Safety",
"Flock": "Flock Safety",
"flock": "Flock Safety",
"floc": "Flock Safety",
"Flock Group Inc.": "Flock Safety",
"Flock Safetu": "Flock Safety",
"Flock Saftey": "Flock Safety",
"Flock Safety Inc": "Flock Safety",
"Flock Surveillance": "Flock Safety",
"Flow Safety": "Flock Safety",
"Motorola": "Motorola Solutions",
"Motorolla": "Motorola Solutions",
"Mortorola Solutions": "Motorola Solutions",
"Motorola/Vigilant": "Motorola Solutions",
"Motorola?": "Motorola Solutions",
"AXIS": "Axis Communications",
"Axis": "Axis Communications",
"axis": "Axis Communications",
"Axis Communications AB": "Axis Communications",
"HIK VISION": "Hikvision",
"HIK Vision": "Hikvision",
"HIK vision": "Hikvision",
"HIKVision": "Hikvision",
"HK Vision": "Hikvision",
"HikVision": "Hikvision",
"hikvision": "Hikvision",
"Hangzhou Hikvision Digital Technology Co., Ltd.": "Hikvision",
"NIK VISION": "Hikvision",
"Hanwha (Wisevision)": "Hanwha",
"Hanwha Techwin": "Hanwha",
"Hahwha Techwin": "Hanwha",
"Wisevision (hanwhavision), série Q ou X": "Hanwha",
"Wisenet": "Hanwha",
"Wisenet Hanwha": "Hanwha",
"Dahua Technology": "Dahua",
"Dahua Technology Co., Ltd": "Dahua",
"dahua": "Dahua",
"Alhua": "Dahua",
"Alhua technology": "Dahua",
"alhua": "Dahua",
"Avigilon": "Avigilon",
"avigilon": "Avigilon",
"Ring Inc": "Ring",
"ring": "Ring",
"Rekor Systems": "Rekor",
"Rektor": "Rekor",
"Reolink Duo 4k 180deg Panorama": "Reolink",
"realink": "Reolink",
"reolink": "Reolink",
"Ubicquia": "Ubicquia",
"Ubiquiti": "Ubiquiti",
"ubiquiti": "Ubiquiti",
"Neology, Inc.": "Neology",
"Neology, Inc": "Neology",
"Leonardo US Cyber and Security Solutions, Inc.": "Leonardo",
"LiveView Technologies": "LVT",
"LifeView Technologies": "LVT",
"Verkada Inc.": "Verkada",
"ZTE Netview": "ZTE NetView",
"Dekom": "DEKOM",
"cyber Secure": "Cyber Secure",
"Cyber Secure?": "Cyber Secure",
"yber Secure?": "Cyber Secure"
}
df["company"] = df["manufacturer"].replace(companyfix)
st.markdown(f"""This loaded **{len(df):,} cameras** from the FLOCK files.""")
#main viz desc
st.header("Are surveilling cameras a growing epidemic?")
st.markdown("Our central interactive visualization allows you explore the FLOCK dataset across the United States by selecting one or more companies from the drop down menu. Each dot on the map represent a single camera record from the dataset correlating with the selected companies on the dropdown. As cameras are plotted with their geographic coordinates, as you toggle with our visualization, you will begin to see different geographic patterns in how vendors have distributed their infrastructure. Our visualization allows you to zoom in and out of the map to get a closer look at specific cities, regions, and states. We encourage you to explore the map with curiosity and a critical eye while receiving a better depiction of the surveillance state in the United States. ")
#The map is the primary viz for the project.
# explain how to use the dropdown to compare companies,
# u can pan and zoom into cities to see where cameras cluster.
# explain how i locked it to America so the map stays focused.
#company filter
companyorder = df["company"].value_counts().index.tolist()
companies = companyorder
selectedcompanies = st.multiselect("Select companies", options= companies, default= companies[:5], help= "The top 5 are shown for clarity, but use this to filter to select which companies as you like :)")
if selectedcompanies:
mapdf = df[df["company"].isin(selectedcompanies)]
else:
mapdf = df.copy()
#main viz code
mapdf = mapdf.sample(min(len(mapdf), 5000), random_state= 676767) #smaller set for performance bc ik my pc is a fair bit better than others laptops
#i locked the map to amercia only bc we are discussing the US.
mapchart = alt.Chart(mapdf).mark_circle(size= 8, opacity= 0.35).encode(
x= alt.X("lon:Q", title= "Longitude", scale= alt.Scale(domain= [-130, -60])),
y= alt.Y("lat:Q", title= "Latitude", scale= alt.Scale(domain= [20, 55])),
color= alt.Color("company:N", title= "Company", sort= companyorder, scale= alt.Scale(domain= companyorder), legend= alt.Legend(columns= 2)),
tooltip= ["name", "company", "operator", "type", "direction", "lat", "lon"]).properties(width= chartwidth, height= mapheight, title= "Camera Records in the United States").interactive()
st.altair_chart(mapchart, width= chartwidth)
st.markdown("Not all surveillance cameras are created equally, and not all of the companies behind them operate the same way. The following two visualizations provide a depiction into the top camera operators, followed by the most common types of cameras which are ultimately differentiated through their purpose. In the bar chart below, the two most common companies are labeled as “Unknown” and Flock Safety. The unknown label represents cameras whose manufacture could not be identified, while Flock Safety is a company that markets its camera products primarily to law enforcement agencies, private communities, and homeowners associations. The association between the companies that deploy these cameras with specific purposes reflects a deliberate system shaped by the interests and contracts of the institutions that fund and operate it. ")
# Distribution over america
# -Look at grouping around cities like our very own chicago or nyc/cali etc, and major intersections.
# -Look at how suburbs have differnt concentrations than rural areas.
# -Ask user or show user how zooming into these cities should change the viewer's perception
# -Why is the distribution so uneaven? (TALK ABOUT REDLINING AND HOW THESE CAMS EXISTS MOSTLY IN LOWER SOCIOECONIC ZONES AND MINORITY NEIGHBORHOODS, AND HOW THIS IS A PROBLEM)
st.divider()
#context viz 1
st.header("Most Common Companies?")
opcounts = df["company"].value_counts().head(15).reset_index()
opcounts.columns = ["company", "count"]
opchart = alt.Chart(opcounts).mark_bar().encode(
x= alt.X("count:Q", title= "Number of camera records"), y= alt.Y("company:N", title= "company", sort= "-x"),
tooltip= ["company", "count"]).properties(width= chartwidth, height= chartheight, title= "Top companys in the Dataset")
st.altair_chart(opchart, width= chartwidth)
#st.markdown("lwokey find a new plot to do this is not good")
#st.caption("fix this caption later or delete it idrc")
st.divider()
#context viz 2
st.header("What types of cameras are we looking at?")
typecounts= df["type"].value_counts().head(12).reset_index()
typecounts.columns = ["type", "count"]
typechart = alt.Chart(typecounts).mark_bar().encode(
x= alt.X("type:N", title= "Camera type", sort= "-y"), y= alt.Y("count:Q", title= "Number of records"), color= alt.Color("type:N", legend= None),
tooltip= ["type", "count"]).properties(width= chartwidth, height= chartheight, title= "Most Common Camera Types")
st.altair_chart(typechart, width= chartwidth)
st.markdown("These camera types reflect a broader trend in public safety technology that acknowledges where the line between observation and intervention is increasingly thin. Understanding what kinds of cameras exist, and in what quantities, is a foundational step in being able to have an informed public conversation about where and how these tools are used. ")
# Company dominance and how we did data cleaning
# -metadata was very inconsistent or missing
# -there were many typo corrections or company names to consolidate.
# -Discuss Flock Safety as the big dawg.
# -Explain why our data cleaning matters for visualization for the public (whatever that means i just am reading the rubric)
# -what are the limitations of data like this (its a crowdsourced and publicly accumulated dataset)
st.caption("Visualization created via the FLOCK camera dataset.")
#come up with better captions my brain was fried at 4am
st.divider()
#extra context
st.header("Why You Should Care")
#once again fix these captions lol
st.markdown("Surveillance infrastructure is not neutral. The choices made about where to place cameras, which companies contract with, and what data to retain are decisions made by institutions and can have real consequences for the communities living within the field of view. Historically, the communities who are most heavily surveilled have also been the most marginalized, this includes low-income neighborhoods, communities of color, and densely populated urban areas (AmericanBarOrg, 2024). Companies such as Flock Safety use cameras such as ALPR’S (Automated License Plate Recognition cameras), don’t simply record, but build a database of movement that can be shared across law enforcement agencies with little public oversight or consent from the people being tracked. As the implementation of this technology steadily increases, its widespread adaption expands into spaces that were considered to be unmonitored and private. These visualizations hope to be a starting point for critical awareness and conversation surrounding surveillance architecture and the communities it most deeply impacts. ")
# our interpretation and the implications
# -Explain how surveillance networks operate at such a massive scale with little (now growing) public awareness check out eyesonflock on google too.
# -What questions did yall have about data ownership and access to surveillance data
# -Mention public vs private surveillance and how the lines are nonexistant. (private companies sell massive datasets to govt for profit rather than catching bad guys.)
# -The viz works better to prove the point and inform rather than advocate some change or whatever altho u can at EFF if u google that.
# -Explain the importance of data transparency, and soutions to this "dragnet" surveillance (i.e. public awareness, policy change, etc. once agian visit EFF)
#sources
st.header("Sources")
st.markdown("""
- FLOCK camera dataset: https://github.com/Ringmast4r/FLOCK
- Visualizations created by us using Python, Altair, and Streamlit.
- Dataset was loaded from the master repository tiled JSON files to avoid the 100mb limit.
- https://www.americanbar.org/groups/crsj/resources/human-rights/2024-june/mass-surveillance-dangerous-american-communities-reforming-section-702/ """) |