공공데이터에 같은 CCTV 목록이 네 번 들어 있었어요: 반복 업로드를 찾아 뺀 규칙
핵심 요약
- 전국CCTV표준데이터에는 일부 기관이 기준일자만 바꿔 다시 올린 목록이 옛 목록과 함께 남아 있어서, 대구 서구청은 1,000·1,300·1,350·1,599행짜리 목록 네 개가 모두 들어 있었어요.
- 관리번호는 업로드마다 새로 매겨지고 뺀 행의
데이터갱신구분도 29,223행 중 29,220행이I라서, 옛 업로드의 행이 같은 기관의 나중 업로드에 같은 주소나 좌표로 80% 이상 다시 나오고 나중 목록의 행 수가 옛 업로드의 80% 이상이면 옛 업로드를 뺐어요. - 이 규칙으로 38개 업로드, 29,223행(카메라 57,324대)을 뺐고 보는지도의 대구 서구는 1,060곳·5,246대에서 936곳·1,593대가 됐어요.
행정안전부 전국CCTV표준데이터에는 같은 기관이 기준일자만 바꿔 다시 올린 목록이 옛 목록과 함께 남아 있어요. 대구 서구의 숫자를 들여다보다가, 제 지도가 서구의 같은 카메라를 네 번까지 세고 있었다는 걸 알았어요. 관리번호가 업로드마다 달라서 행 단위 중복 제거로는 걸러지지 않았고, 업로드끼리 같은 주소나 좌표가 얼마나 겹치는지로 찾았어요. 그렇게 뺀 업로드가 38개, 29,223행이에요.
원본 파일에서 센 숫자는 모두 2026년 9월 20일 내려받은 데이터(381,036행) 기준이에요.
대구 서구만 한 곳에 카메라가 약 5대
대구의 구·군별 숫자를 정리하다가 서구에서 멈췄어요. 보는지도에 서구는 설치지점 1,060곳, 카메라 5,246대로 나와서 한 곳에 평균 4.9대가 달려 있었어요. 대구의 다른 구·군은 1.2~2.0대, 대구 전체 평균은 1.9대였어요. 기둥 하나에 카메라 다섯 대가 흔한 구성은 아니라서 원본을 열어 봤더니, 관리기관명이 대구광역시 서구청인 행에 데이터기준일자가 네 가지 있었어요.
데이터기준일자 | 행 수 | 마지막 목록에 같은 주소·좌표가 있는 행 | 마지막 목록과 같은 관리번호 |
|---|---|---|---|
| 2023-12-18 | 1,000 | 99.7% | 0건 |
| 2024-07-18 | 1,300 | 99.8% | 0건 |
| 2025-01-01 | 1,350 | 100% | 0건 |
| 2025-11-06 | 1,599 | (마지막 목록) | - |
앞의 세 목록은 거의 모든 행이 마지막 목록에 다시 나와요. 도로명주소만 비교해도 92~99%가 같아요. 목록이 해마다 조금씩 늘어난 모양이라, 새로 받은 목록이 옛 목록을 대신하는 게 아니라 옆에 쌓인 셈이에요.
보는지도는 좌표와 설치 목적, 관리기관이 같은 행을 한 지점으로 합치고 카메라대수를 더해요. 같은 좌표에 카메라를 여러 행으로 나눠 적는 기관이 많아서 정한 방식인데, 같은 목록이 네 번 들어 있으면 같은 카메라를 네 번 더하게 돼요. 좌표가 조금이라도 다르면 따로 점이 찍히고요. 서구의 1,060곳과 5,246대는 그렇게 나온 숫자였어요.
관리번호와 갱신 구분으로는 왜 안 잡힐까
처음에는 관리번호로 거르면 될 줄 알았어요. 그런데 서구청의 앞선 세 목록에 있는 관리번호는 마지막 목록에 하나도 없었어요. 같은 카메라도 업로드할 때마다 새 번호를 받아서 관리번호만 보면 전부 다른 카메라예요.
파일에는 데이터갱신구분 칸도 있지만 이것도 도움이 되지 않았어요. 서구청의 네 목록은 모두 I이고 데이터갱신시점도 2026-05-15로 같아요. 나중에 뺀 29,223행으로 넓혀 봐도 29,220행이 I, 3행이 빈칸이에요. 파일 안에서는 "이 목록이 저 목록을 대신한다"는 표시를 찾을 수 없었어요.
그래서 기관이 올린 목록을 묶음 단위로 비교하기로 했어요. 한 행 한 행이 아니라 업로드 하나가 다른 업로드에 통째로 다시 들어 있는지를 보는 거예요.
업로드를 묶어서 비교하는 규칙
규칙은 세 단계예요.
- 관리기관(
개방자치단체코드+관리기관명)마다 행을데이터기준일자별로 나눠요. 이 묶음 하나가 업로드 하나예요. - 업로드마다 같은 기관의 더 나중 업로드 전체와 비교해, 도로명주소나 지번주소가 같거나 좌표가 소수점 5자리(약 1m)까지 같은 행의 비율을 세요.
- 그 비율이 80% 이상이고 나중 업로드의 행 수가 그 업로드의 80% 이상이면, 옛 목록으로 보고 가져오지 않아요.
보는지도 데이터 파이프라인의 실제 코드에서 기준값과 비교 키를 만드는 부분이에요.
export const SUPERSEDED_MATCH_SHARE = 0.8;
export const SUPERSEDED_LATER_SIZE = 0.8;
function coordinateKey(row: Row, config: SourceConfig) {
const latitude = finiteNumber(row[config.fields.latitude]); const longitude = finiteNumber(row[config.fields.longitude]);
return latitude == null || longitude == null ? null : `${latitude.toFixed(5)},${longitude.toFixed(5)}`;
}
function rowKeys(row: Row, config: SourceConfig) {
const keys: string[] = [];
for (const field of [config.fields.roadAddress, config.fields.lotAddress]) {
const value = field ? text(row[field]) : "";
if (value) keys.push(`a:${value}`);
}
const coordinate = coordinateKey(row, config);
if (coordinate) keys.push(`c:${coordinate}`);
return keys;
}
판단하는 부분만 옮기면 이래요(빠진 업로드를 기록하는 줄은 뺐어요).
for (const [agency, byDate] of uploads) {
const dates = [...byDate.keys()].sort();
for (let index = 0; index < dates.length - 1; index++) {
const later = dates.slice(index + 1).flatMap(date => byDate.get(date)!);
const laterKeys = new Set(later.flatMap(row => rowKeys(row, config)));
const rows = byDate.get(dates[index])!;
const matched = rows.filter(row => rowKeys(row, config).some(key => laterKeys.has(key))).length;
const matchShare = matched / rows.length;
if (matchShare < SUPERSEDED_MATCH_SHARE || later.length < rows.length * SUPERSEDED_LATER_SIZE) continue;
for (const row of rows) dropped.add(row);
}
}
한 행에서 나온 키 가운데 하나만 같아도 같은 행으로 봐요. 키를 한 종류만 쓰면 놓치는 업로드가 생겨요. 주소만 쓰면 38개 중 35개, 좌표만 쓰면 29개를 찾아요. 다시 올린 목록에서 좌표가 바뀐 기관도 있고 주소 표기가 바뀐 기관도 있어서 둘 다 봐요.
관리기관을 코드만으로 묶지 않는 데도 이유가 있어요. 경기도 하남시는 코드 하나 아래에서 정보통신과, 자원순환과, 안전정책과, 상수도과 같은 부서가 각자 목록을 올리고, 2024-01-30처럼 여러 부서가 같은 날 올린 경우도 있어요. 코드로만 묶으면 그런 목록이 한 업로드로 합쳐져서, 전국 결과가 34개 업로드, 29,584행으로 달라져요.
80% 기준을 바꿔 보면
자치단체 코드와 관리기관명 조합으로 센 1,836개 관리기관 가운데 기준일자를 두 개 이상 쓴 곳은 60곳이에요. 그 기관들의 업로드 가운데 마지막이 아닌 122개를 모두 비교해 봤어요.
비율이 양쪽 끝에 몰려 있어요. 겹치는 행이 20%도 안 되는 업로드는 나중 업로드가 같은 목록을 다시 올린 게 아니라 다른 카메라를 올린 경우로 보여서 남겨요. 90% 이상인 34개 중 33개는 다시 올린 목록이고, 나머지 하나는 아래에 나오는 영광군 사례예요. 20~80% 구간에는 10개뿐이에요.
기준값만 바꿔 다시 돌린 결과예요.
| 겹침 기준 | 빼는 업로드 | 빼는 행 |
|---|---|---|
| 50% | 42 | 30,048 |
| 60% | 40 | 30,016 |
| 70% | 38 | 29,223 |
| 80% (사용 중) | 38 | 29,223 |
| 90% | 33 | 27,934 |
| 95% | 30 | 25,641 |
50%에서 80% 사이에서는 빼는 행이 825행 안에서 움직이고, 70%와 80%는 결과가 같아요. 90% 위로 올리면 차이가 커져요. 90%에서는 겹침이 80%대인 다섯 업로드가 남아서 그만큼 같은 카메라가 두 번 세어져요.
겹침은 높은데 다시 올린 목록이 아닌 경우
두 번째 조건(나중 목록이 충분히 커야 한다)은 겹침 비율이 실제와 다르게 높게 나오는 업로드를 걸러요. 영광군청의 2021-10-20 업로드는 390행이 모두 같은 좌표(35.2677, 126.5516)를 쓰고 있어요. 다음 업로드의 32행도 같은 좌표라서 겹침 비율은 100%가 나오지만, 나중 업로드를 다 합쳐도 192행뿐이에요. 다시 올린 목록으로 볼 수 없어서 남겨요.
두 조건으로도 잡지 못하는 경우가 있어요. 목록의 일부만 다시 올렸거나 주소 표기와 좌표가 한꺼번에 바뀌었다면 겹침이 낮게 나와서 옛 목록이 남아요. 강원 태백시청의 2024-07-01 업로드는 10,296행인데 좌표는 534곳뿐이고, 나중 업로드는 343행, 겹침은 63%예요. 두 조건 모두에 못 미쳐 남겼지만, 그 안에 다시 올린 행이 섞여 있을 수 있어요. 같은 기관의 2023-04-01 업로드(5,048행)는 겹침 100%라 빠졌어요. 원본을 새로 받을 때마다 빠지는 업로드와 남는 업로드 목록을 다시 확인해요.
다른 자료로 맞춰 본 결과
규칙이 맞는지 원본만 보고 판단하기는 어려워서 행정안전부가 따로 내는 「지방정부 통합관제센터 CCTV 운영 현황」('25.12.31. 기준)과 비교했어요. 이 표는 통합관제센터에 연결된 CCTV만 세요. 대구는 '남구 (일부 통합관제)' 한 줄에 12,324대인데, 보안뉴스(2022-10-04) 기사에 따르면 대구광역시 CCTV 통합관제센터에는 수성구와 달성군을 뺀 6개 자치구의 관제센터가 들어 있어요. 중·동·서·남·북·달서구 여섯 곳을 합쳐서 봤어요.
차이가 31%에서 2% 아래로 줄었어요. 다만 두 자료는 세는 범위가 달라요. 규칙의 영향을 받지 않은 수성구(3,740대 대 3,149대), 달성군(1,680대 대 2,109대), 군위군(1,242대 대 940대)은 19~32% 어긋나 있어요. 그래서 이 비교는 정황으로만 보고 증명으로 쓰지는 않아요.
보는지도에 반영한 결과
보는지도 웹 지도는 2026년 10월 19일부터 이 규칙을 적용했고, Android 앱도 1.1.0 업데이트부터 같은 데이터를 보여 줘요. 지도 전체 숫자는 규칙을 적용하기 전 258,376곳·697,562대에서 253,980곳·640,238대로 바뀌었고, 숫자가 달라진 시·군·구는 48곳이에요. 빠진 카메라 57,324대는 앞의 업로드들에 적혀 있던 카메라대수를 더한 값이에요.

공개 데이터의 다른 문제(같은 좌표에 몰린 행, 더미 좌표, 비어 있는 설치연월)는 공공데이터의 CCTV 개수는 실제 CCTV 수일까?에 정리했어요. 기관마다 기준일이 어떻게 다른지는 전국 CCTV 데이터는 얼마나 최신일까?에서 볼 수 있어요.
분석 방법
2026년 9월 20일 내려받은 CSV(CP949, 381,036행) 전체를 대상으로, 보는지도 파이프라인과 별도로 같은 규칙을 Python으로 다시 구현해 셌어요. 주소는 공백을 하나로 줄인 뒤 비교했고, 좌표는 소수점 5자리로 반올림했어요. 결과는 2026년 10월 19일 지도에 규칙을 적용할 때 파이프라인이 센 숫자와 같아요. 지도 숫자는 보는지도 지역 페이지의 적용 전·후 값이에요.
출처
- 전국CCTV표준데이터, 행정안전부, 공공데이터포털 (2026-09-20 내려받음)
- 지방정부 통합관제센터 CCTV 운영 현황('25.12.31.), 행정안전부, 재난안전데이터공유플랫폼 (2026-10-24 파일 미리보기로 확인)
- [현장탐방] 대구 북구 CCTV통합관제센터, 보안뉴스, 2022-10-04
자주 묻는 질문
다른 공공데이터에서도 다시 올린 목록이 있는지 빠르게 확인하려면?
기관 코드와 기준일자 칸이 있는 표준데이터라면, 기관마다 기준일자가 몇 가지인지부터 세어 보면 돼요. 기준일자가 하나뿐인 기관은 다시 올린 목록이 있을 수 없어서, 비교할 대상이 금방 줄어요. 전국CCTV표준데이터는 CP949라 encoding="cp949"로 읽어야 해요(엄격한 EUC-KR로 읽으면 바이트 오프셋 420,434에서 실패해요).
import csv
from collections import defaultdict
dates = defaultdict(set)
with open("national-cctv.csv", encoding="cp949", newline="") as f:
for row in csv.DictReader(f):
agency = (row["개방자치단체코드"], row["관리기관명"].strip())
dates[agency].add(row["데이터기준일자"])
several = {a: sorted(d) for a, d in dates.items() if len(d) > 1}
print(len(dates), "agencies,", len(several), "with more than one reference date")
2026년 9월 20일 파일에서는 1836 agencies, 60 with more than one reference date가 나와요.