AI Analyzer는 서울시 상권 데이터를 기반으로 특정 위치의 상업적 잠재력을 분석하고, AI 모델을 통해 해당 점포의 장기 생존 확률을 예측하는 웹 서비스입니다.
- 정확한 위치 기반 분석: EPSG:5186 좌표계 사용으로 한국 지역에 최적화
- 다층적 상권 분석: 인구, 시설, 경쟁업체, 지가 등 종합 분석
- AI 기반 예측: XGBoost 모델을 통한 생존 확률 예측
- 직관적 사용자 인터페이스: 카카오 API 연동 주소 입력 및 시각화
-
인구 분석
- 생활인구: 반경 300m/1000m 내 연령대별 인구 분포
- 직장인구: 반경 300m 내 직장인구 수
- 외국인 인구: 단기/장기 체류별, 국적별 분석
-
시설 분석
- 교육시설: 반경 250m 내 학교 수
- 공공시설: 반경 250m 내 공공건물 수
-
경쟁 환경 분석
- 동일 업종 경쟁업체 수
- 전체 요식업체 수
- 업종 다양성 지수
- 경쟁 비율 계산
-
부동산 분석
- 공시지가 기반 토지 가치 평가
- 면적별 총 토지가치 산출
- 모델: XGBoost 분류 모델
- 입력 피쳐: 27~28개 상권 분석 지표
- 출력: 장기 생존 확률 (0~100%)
- 신뢰성: Feature shape mismatch 대응 fallback 로직
- 주소 입력: 카카오 Local API 연동 자동 좌표 변환
- 시각화: Chart.js 기반 분석 결과 차트
- 결과 요약: 강점 및 주의사항 요약 제공
- Framework: Django 5.2
- Language: Python 3.x
- Database: SpatiaLite (SQLite + Spatial Extensions)
- GIS: GeoDjango, GDAL/OGR
- Model: XGBoost
- Libraries: Scikit-learn, Pandas, NumPy
- Coordinate System: pyproj (좌표계 변환)
- UI Framework: Bootstrap 5
- Charting: Chart.js
- Maps: Kakao Local API
- Spatial Engine: OSGeo4W
- Data Format: GPKG (GeoPackage)
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ Django App │ │ Data Layer │
│ │ │ │ │ │
│ • HTML/CSS/JS │◄──►│ • Views │◄──►│ • SpatiaLite │
│ • Bootstrap │ │ • Models │ │ • GPKG Files │
│ • Chart.js │ │ • URL Routes │ │ • AI Model │
│ • Kakao API │ │ │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ ┌─────────────────┐ │
└──────────────►│ External APIs │◄─────────────┘
│ │
│ • Kakao Local │
│ • OSGeo4W │
└─────────────────┘
- Python 3.x
- Git
# 프로젝트 클론
git clone <repository-url>
cd AI-Analyzer
# 가상환경 생성 및 활성화
python -m venv venv
# Windows
venv\Scripts\activate
# macOS/Linux
source venv/bin/activate
# 의존성 설치
pip install -r requirements.txt# 프로젝트 루트에 spatialite 폴더 생성
mkdir spatialite
mkdir spatialite/bin
# OSGeo4W bin 디렉토리 내용을 spatialite/bin으로 복사
# 필수 파일: mod_spatialite.dll, ogr2ogr.exe, 관련 DLL들# data 폴더 생성 및 GPKG 파일 배치
mkdir data
# 8개 GPKG 파일을 data/ 폴더에 복사# 마이그레이션 생성 및 적용
python manage.py makemigrations locai
python manage.py migrate
# GPKG 데이터 로드
python load_gpkg_data.pypython manage.py runserver서비스는 http://127.0.0.1:8000에서 접근 가능합니다.
POST /get_coordinates/
Content-Type: application/json
{
"address": "서울특별시 강남구 역삼동 123-45"
}응답:
{
"longitude": 127.0276,
"latitude": 37.4979,
"x_coord": 958123.45,
"y_coord": 1943567.89
}POST /analyze/
Content-Type: application/json
{
"address": "서울특별시 강남구 역삼동 123-45",
"area": 50.0,
"business_type_id": 1,
"service_type": 0,
"longitude": 127.0276,
"latitude": 37.4979,
"x_coord": 958123.45,
"y_coord": 1943567.89
}응답:
{
"success": true,
"request_id": 123,
"results": {
"1A_Total": 1500,
"Working_Pop": 800,
"survival_percentage": 75.3,
// ... 기타 분석 결과
}
}GET /result/<int:request_id>/class BusinessType(models.Model):
id = models.IntegerField(primary_key=True) # 업종 ID
name = models.CharField(max_length=100) # 업종명class AnalysisRequest(models.Model):
address = models.CharField(max_length=500) # 분석 주소
area = models.FloatField() # 점포 면적(㎡)
business_type = models.ForeignKey(BusinessType) # 선택 업종
service_type = models.IntegerField() # 서비스 유형
longitude = models.FloatField() # WGS84 경도
latitude = models.FloatField() # WGS84 위도
x_coord = models.FloatField() # EPSG:5186 X좌표
y_coord = models.FloatField() # EPSG:5186 Y좌표
created_at = models.DateTimeField(auto_now_add=True)class AnalysisResult(models.Model):
request = models.OneToOneField(AnalysisRequest)
# 생활인구 관련
life_pop_300m = models.IntegerField() # 300m 총 생활인구
life_pop_20_300m = models.FloatField() # 300m 20대 비율
# ... 연령대별 비율 필드들
# 외국인 관련
temp_foreign_1000m = models.IntegerField() # 1000m 단기체류 외국인
long_foreign_300m = models.IntegerField() # 300m 장기체류 외국인
# ... 기타 외국인 관련 필드들
# 직장인구
working_pop_300m = models.IntegerField() # 300m 직장인구
# 주변시설
public_building_250m = models.IntegerField() # 250m 공공건물 수
school_250m = models.IntegerField() # 250m 학교 수
# 상권분석
competitor_300m = models.IntegerField() # 300m 경쟁업체 수
adjacent_biz_300m = models.IntegerField() # 300m 전체 요식업체 수
business_diversity_300m = models.FloatField() # 업종 다양성
competitor_ratio_300m = models.FloatField() # 경쟁 비율
# 공시지가
total_land_value = models.FloatField() # 총 토지가치
# AI 예측
survival_probability = models.FloatField() # 생존 확률 (0.0~1.0)
survival_percentage = models.FloatField() # 생존 확률 (0~100%)
created_at = models.DateTimeField(auto_now_add=True)이 함수는 AI Analyzer의 핵심 분석 엔진으로, 6단계의 공간 분석을 순차적으로 수행합니다.
analysis_request: AnalysisRequest 객체 (사용자 입력 정보)
1단계: 생활인구 분석
-- 반경 300m/1000m 내 연령대별 생활인구 분석
SELECT SUM(population_20s), SUM(population_30s), ..., SUM(total_population)
FROM life_pop_grid_10m_5186
WHERE ST_Intersects(geom, ST_Buffer(ST_GeomFromText('POINT(x y)', 5186), radius))2단계: 직장인구 분석
-- 반경 300m 내 직장인구 수
SELECT SUM(working_population)
FROM workgrid_10m_5186
WHERE ST_Intersects(geom, ST_Buffer(ST_GeomFromText('POINT(x y)', 5186), 300))3단계: 외국인 분석
-- 단기/장기 체류 외국인 분석 (다중 테이블 확인)
SELECT SUM("총생활인구수"), SUM("중국인체류인구수")
FROM {temp_table_name}
WHERE ST_Intersects(geom, ST_Buffer(ST_GeomFromText('POINT(x y)', 5186), radius))4단계: 주변시설 분석
-- 반경 250m 내 교육/공공시설 수
SELECT COUNT(*)
FROM public_5186/school_5186
WHERE ST_Intersects(geom, ST_Buffer(ST_GeomFromText('POINT(x y)', 5186), 250))5단계: 경쟁업체 분석
-- 반경 300m 내 동일업종 경쟁업체 및 전체 요식업체 분석
SELECT COUNT(*) as competitor_count
FROM store_point_5186
WHERE uptaenm = '{business_type_name}'
AND ST_Intersects(geom, ST_Buffer(ST_GeomFromText('POINT(x y)', 5186), 300))6단계: 공시지가 분석
-- 해당 지점의 공시지가 조회 및 총 토지가치 계산
SELECT "A9" as land_price
FROM ltv_5186
WHERE ST_Contains(geom, ST_GeomFromText('POINT(x y)', 5186))
-- total_land_value = area * land_price- 각 단계의 결과를
results딕셔너리에 저장 - AI 모델용 피쳐 딕셔너리 구성
predict_survival_probability()호출- 모든 결과를
AnalysisResult객체로 DB 저장
- 알고리즘: XGBoost Classifier
- 입력 피쳐: 27~28개 상권 분석 지표
- 출력: 생존 확률 (0.0~1.0)
features = [
'Area', 'Adjacent_BIZ', '1A_Total', 'Total_LV', 'Business_D',
'Working_Pop', '2A_20', '2A_30', '2A_40', '2A_50', '2A_60',
'1A_20', '1A_30', '1A_40', '1A_50', '1A_60',
'1A_Long_Total', '2A_Long_Total', '1A_Temp_CN', '2A_Temp_CN',
'2A_Temp_Total', '2A_Long_CN', 'Competitor_C', 'Competitor_R',
'Service', 'School', 'PubBuilding', 'UPTAENM_ID'
]try:
# 28개 피쳐로 예측 시도 (업종 ID 포함)
prediction = model.predict_proba(features_28)
except:
# 27개 피쳐로 재시도 (업종 ID 제외)
prediction = model.predict_proba(features_27)- 주소 → WGS84: 카카오 Local API 사용
- WGS84 → EPSG:5186: pyproj 라이브러리 사용
# WGS84 → EPSG:5186 변환
transformer = pyproj.Transformer.from_crs(
'EPSG:4326', # WGS84
'EPSG:5186', # Korea 2000 / Central Belt 2010
always_xy=True
)
x_coord, y_coord = transformer.transform(longitude, latitude)| 파일명 | 테이블명 | 용도 | 주요 컬럼 |
|---|---|---|---|
life_pop_grid_10m_5186.gpkg |
life_pop_grid_10m_5186 | 생활인구 분석 | 연령대별 인구수 |
workgrid_10m_5186.gpkg |
workgrid_10m_5186 | 직장인구 분석 | 직장인구수 |
temp_25m_5186.gpkg |
temp_25m_5186 | 단기체류 외국인 | "총생활인구수", "중국인체류인구수" |
long_25m_5186.gpkg |
long_25m_5186 | 장기체류 외국인 | "총생활인구수", "중국인체류인구수" |
store_point_5186.gpkg |
store_point_5186 | 상점 정보 | uptaenm (업종명) |
school_5186.gpkg |
school_5186 | 학교 정보 | 학교 위치 |
ltv_5186.gpkg |
ltv_5186 | 공시지가 | "A9" (지가) |
public_5186.gpkg |
public_5186 | 공공건물 | 공공건물 위치 |
# GPKG → SpatiaLite 변환
python load_gpkg_data.py
# 외국인 데이터 업데이트
python update_foreign_data.py
# 테스트 데이터 생성
python create_test_foreign_data.py# .env 파일 생성
DEBUG=False
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
SECRET_KEY=your-secret-key
KAKAO_API_KEY=your-kakao-api-key# settings.py 추가
STATIC_ROOT = '/path/to/static/'
MEDIA_ROOT = '/path/to/media/'
# 정적 파일 수집
python manage.py collectstatic# Gunicorn 설치
pip install gunicorn
# Gunicorn 실행
gunicorn geoproject.wsgi:application --bind 0.0.0.0:8000server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /static/ {
alias /path/to/static/;
}
}FROM python:3.11
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput
EXPOSE 8000
CMD ["gunicorn", "geoproject.wsgi:application", "--bind", "0.0.0.0:8000"]증상: OSError: cannot load library 'mod_spatialite': error
해결방법:
# 1. spatialite 폴더 구조 확인
spatialite/
└── bin/
├── mod_spatialite.dll
├── ogr2ogr.exe
└── [기타 DLL 파일들]
# 2. settings.py 경로 확인
OSGEO4W_ROOT = os.path.join(BASE_DIR, 'spatialite')
SPATIALITE_LIBRARY_PATH = os.path.join(OSGEO4W_ROOT, 'bin', 'mod_spatialite.dll')증상: ogr2ogr: command not found
해결방법:
# 1. ogr2ogr.exe 위치 확인
ls spatialite/bin/ogr2ogr.exe
# 2. PATH 환경변수 확인 (settings.py)
if os.path.exists(os.path.join(OSGEO4W_ROOT, 'bin')):
os.environ['PATH'] = os.path.join(OSGEO4W_ROOT, 'bin') + ';' + os.environ['PATH']증상: no such column: 총생활인구수
해결방법:
# 테이블 컬럼 확인
cursor.execute("PRAGMA table_info(temp_25m_5186)")
columns = cursor.fetchall()
print(columns)
# 올바른 컬럼명 사용
cursor.execute("SELECT SUM(\"총생활인구수\") FROM temp_25m_5186 WHERE ...")증상: FileNotFoundError: model/best_xgb_model.pkl
해결방법:
# 1. 모델 파일 존재 확인
ls model/best_xgb_model.pkl
# 2. 파일 권한 확인
chmod 644 model/best_xgb_model.pkl증상: InvalidTransformDefinition: Invalid coordinate system
해결방법:
# pyproj 버전 확인 및 업데이트
pip install --upgrade pyproj
# 좌표계 정의 확인
from pyproj import CRS
crs = CRS.from_epsg(5186)
print(crs.is_valid)-- 공간 인덱스 생성
CREATE INDEX idx_life_pop_geom ON life_pop_grid_10m_5186(geom);
CREATE INDEX idx_store_geom ON store_point_5186(geom);
CREATE INDEX idx_store_uptaenm ON store_point_5186(uptaenm);# settings.py
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
# views.py에서 캐싱 사용
from django.core.cache import cache
def load_xgboost_model():
model = cache.get('xgboost_model')
if model is None:
model = pickle.load(open('model/best_xgb_model.pkl', 'rb'))
cache.set('xgboost_model', model, 3600) # 1시간 캐싱
return model이 프로젝트는 MIT 라이센스 하에 배포됩니다.
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature) - Commit your Changes (
git commit -m 'Add some AmazingFeature') - Push to the Branch (
git push origin feature/AmazingFeature) - Open a Pull Request
문제가 발생하거나 질문이 있으시면 이슈를 등록해주세요.
개발팀 연락처: support@locai.com