Files
fmviewer3/project/fm_viewer/data/fm_address.cpp
2026-02-21 17:11:31 +09:00

1150 lines
42 KiB
C++

#include "fm_address.h"
#include <QDir>
#include <QCoreApplication>
#include <QDebug>
#if (USE_JP_ADDRESS_TOOL)
#include <QElapsedTimer>
#include <QThread>
#include <QFile>
#include <wchar.h>
#include <QDateTime>
#include <qmath.h>
#include <math.h>
#endif
// TYPE 0: 국토지리원 1. 전자 국토 기본도 (지명 정보) "주거 표시 주소" (2200만건)
// TYPE 1: 기존DB 1100만건
// TYPE 2: 국토교통성 지명DB https://nlftp.mlit.go.jp/cgi-bin/isj/dls/_choose_method.cgi
#if (USE_JP_ADDRESS)
// 비트팩->바이트 계산
inline uint32_t _ABitsPackToByte(uint8_t* bits) {
return ceil((double)(bits[0] + bits[1] + bits[2] + bits[3])/8.0);
}
// 영역 포함 확인
inline bool _AIsInArea(JA_AREA_PACKET* area,int x, int y, int tolerance) {
return (x >= (area->xmin - tolerance) &&
x <= (area->xmax + tolerance) &&
y >= (area->ymin - tolerance) &&
y <= (area->ymax + tolerance));
}
#if (USE_JP_ADDRESS_TOOL)
#define PROCESS_TEST_CITY 0
QMap<QString,uint32_t> FMAddress::_stringTable;
#endif // USE_JP_ADDRESS_TOOL
FMAddress::FMAddress()
{
memset(&_header,0,sizeof(_JA_HEADER));
_areas = NULL;
_strings = NULL;
_file = NULL;
#if (USE_JP_ADDRESS_TOOL)
_maxLenPref = 0;
_maxLenCity = 0;
_maxLenChome = 0;
_maxA0 = 0;
_maxA1 = 0;
QString dateString = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
strcpy(_header.header,"F-REVERSE ADDRESS GEOCODING TABLE API");
strcpy(_header.date,dateString.toLocal8Bit().constData());
strcpy(_header.version,"0.1.0");
qInfo() << _header.header << _header.date << _header.version;
_prefs = NULL;
#endif
}
FMAddress::~FMAddress()
{
if(_areas != NULL) {
free(_areas);
}
if(_strings != NULL) {
free(_strings);
}
if(_file != NULL) {
fclose(_file);
}
}
bool FMAddress::open(QString path)
{
_file = fopen(path.toLocal8Bit(),"rb");
if(_file == NULL) {
return false;
}
fread(&_header,sizeof(_JA_HEADER),1,_file);
_strings = (uint8_t*)malloc(_header.stringSize);
fread(_strings,_header.stringSize,1,_file);
uint32_t areaSize = sizeof(_JA_AREA_PACKET) * (_header.pref_count + _header.city_count + _header.town_count);
_areas = (JA_AREA_PACKET*)malloc(areaSize);
fread(_areas,areaSize,1,_file);
#if (USE_JP_ADDRESS_TOOL)
qInfo() << "-----------------------------------------------------";
qInfo() << "HEADER : Bound:" << _header.xmin << _header.ymin << _header.xmax << _header.ymax;
qInfo() << "HEADER : Bound(Lon/Lat):" << QString().sprintf("%.6f,%.6f,%.6f,%.6f",(double)_header.xmin / 80000.0,(double)_header.ymin / 120000.0,(double)_header.xmax / 80000.0,(double)_header.ymax / 120000.0);
qInfo() << "HEADER : ID/DATE/VERSION:" << _header.header << _header.date << _header.version;
qInfo() << "HEADER : SIZE:" << QString().sprintf("0x%X",sizeof(_JA_HEADER));
qInfo() << "HEADER : STRING SIZE:" << _header.stringSize;
qInfo() << "HEADER : PREF,COUNT:" << _header.pref_count;
qInfo() << "HEADER : CITY,COUNT:" << _header.city_count;
qInfo() << "HEADER : TOWN,COUNT:" << _header.town_count;
qInfo() << "HEADER : JIBUN,COUNT:" << _header.jibun_count;
//debugList();
#endif
return true;
}
bool FMAddress::open()
{
if(!isOpened()) {
QString path = QDir::cleanPath(QCoreApplication::applicationDirPath() + QDir::separator() + "jpaddr.bin");
if(!open(path)) {
open("C://home//roadmovie//script//jpaddr//20210120_jp_addr.bin");
}
}
return isOpened();
}
bool FMAddress::search(double lon, double lat,QStringList& result,int tolerance)
{
wchar_t* p = NULL;
wchar_t* c = NULL;
wchar_t* t = NULL;
int a0 = 0;
int a1 = 0;
int dist = 0;
if(search(lon,lat,&p,&c,&t,&a0,&a1,&dist,tolerance) == false) {
return false;
}
result.append(QString::fromUtf16((ushort*)p));
result.append(QString::fromUtf16((ushort*)c));
result.append(QString::fromUtf16((ushort*)t));
result.append(QString::number(a0));
result.append(QString::number(a1));
result.append(QString::number(dist));
return true;
}
bool FMAddress::search(double lon,double lat,wchar_t** pref,wchar_t** city,wchar_t** town,int* a0, int*a1, int* pdist, int tolerance)
{
qInfo() << QString().sprintf("SEARCH START :%.6f,%.6f [%d]",lat,lon,tolerance);
_found_a0 = -1;
_found_a1 = -1;
_found_dist_sq = 1000000; // 거리^2
int x = (int)(lon * 80000.0);
int y = (int)(lat * 120000.0);
bool bFound = false;
for(uint32_t p=0;p<_header.pref_count;p++) {
JA_AREA_PACKET* pp = &_areas[p];
//qInfo() << "PREF" << QString::fromUtf16((ushort*)&_strings[pp->nameOffset]) << pp->xmin << pp->ymin << pp->xmax << pp->ymax;
// PREF 포함
if(_AIsInArea(&_areas[p],x,y,tolerance)) {
//qInfo() << QString::fromUtf16((ushort*)&_strings[pp->nameOffset]);
uint32_t cidx = pp->sub_offset / sizeof(_JA_AREA_PACKET);
for(uint32_t c=0;c<pp->sub_count;c++) {
JA_AREA_PACKET* cc = &_areas[cidx + c];
// CITY 포함
//qInfo() << "\t" << QString::fromUtf16((ushort*)&_strings[cc->nameOffset]);
if(_AIsInArea(cc,x,y,tolerance)) {
uint32_t tidx = cc->sub_offset / sizeof(_JA_AREA_PACKET);
for(uint32_t t=0;t<cc->sub_count;t++) {
JA_AREA_PACKET* tt = &_areas[tidx + t];
if(_AIsInArea(tt,x,y,tolerance)) {
//qInfo() << "\t\t" << QString::fromUtf16((ushort*)&_strings[tt->nameOffset]);
if(_searchTown(tt,x,y,tolerance)) // 가장 가까운 지점이 발견된 경우
{
bFound = true;
//qInfo() << "\t\t\t" << "FOUND!!!!" << QString::fromUtf16((ushort*)&_strings[tt->nameOffset]) << (int)sqrt((double)_found_dist_sq) ;
// 리턴
*pref = (wchar_t*)&_strings[pp->nameOffset];
*city = (wchar_t*)&_strings[cc->nameOffset];
*town = (wchar_t*)&_strings[tt->nameOffset];
*pdist = (int)sqrt((double)_found_dist_sq);
*a0 = _found_a0;
*a1 = _found_a1;
}
} // IN TOWN
} // FOR TOWN
} // IN CITY
} // FOR CITY
} // IN PREF
} // FOR PREF
return bFound;//(_found_dist_sq < tolerance);
}
bool FMAddress::_searchTown(JA_AREA_PACKET* town,int x, int y, int tolerance)
{
const int lx = x - town->xmin;
const int ly = y - town->ymin;
JA_AREA_PACKET box = {0,};
box.xmin = (lx - tolerance);
box.ymin = (ly - tolerance);
box.xmax = (lx + tolerance);
box.ymax = (ly + tolerance);
uint32_t bytes = _ABitsPackToByte(town->bits); // 1 지번당 바이트
const uint32_t xm = ((1 << town->bits[0])-1); // xmask
const uint32_t ym = ((1 << town->bits[1])-1); // ymask
const uint32_t a0m = ((1 << town->bits[2])-1); // a0mask
const uint32_t a1m = ((1 << town->bits[3])-1); // a1mask
// A0-A1-x-y shift
const uint32_t xs = town->bits[1] + town->bits[2] + town->bits[3];
const uint32_t ys = town->bits[2] + town->bits[3]; // a0
const uint32_t a0s = town->bits[3]; // a0
const uint32_t a1s = 0; // a1
// 할당 @TODO -> MAX_TOWN_BUFFER 를 생성해서 처리 가능..
uint8_t* buffer = (uint8_t*)malloc(bytes * town->sub_count);
uint64_t v; // 데이터 처리용
fseek(_file,town->sub_offset,SEEK_SET);
fread(buffer,bytes * town->sub_count,1,_file);
bool bFound = false;
for(uint32_t j=0;j<town->sub_count;j++)
{
v = 0;
memcpy(&v,&buffer[bytes*j],bytes);
int vx = ((v >> xs) & xm);
int vy = ((v >> ys) & ym);
// 영역 포함
if(_AIsInArea(&box,vx,vy,0))
{
int ds2 = ((vx - lx) * (vx - lx)) + ((vy - ly) * (vy - ly));
if(ds2 < _found_dist_sq) {
_found_dist_sq = ds2;
_found_a0 = (v >> a0s) & a0m;
_found_a1 = (v >> a1s) & a1m;
_found_x = town->xmin + vx;
_found_y = town->ymin + vy;
//qInfo() << "FOUND:" << _found_dist_sq << "JIBUN:" << _found_a0 << _found_a1;
bFound = true;
}
}
// half binary search
if(vx > box.xmax) {
break;
}
//qInfo() << "\t\t\t" << QString().sprintf("#%d %d-%d %d,%d (%.6f,%.6f)",j+1,va0,va1,vx,vy,((double)(town->xmin + vx))/80000.0,((double)(town->ymin + vy))/120000.0);
}
free(buffer);
return bFound;
}
#if (USE_JP_ADDRESS_TOOL)
void FMAddress::debugList()
{
for(uint32_t p=0;p<_header.pref_count;p++) {
JA_AREA_PACKET* pp = &_areas[p];
qInfo() << QString::fromUtf16((ushort*)&_strings[pp->nameOffset]) << QString().sprintf("cities:%d bound: %.6f,%.6f,%.6f,%.6f",pp->sub_count,((double)pp->xmin)/80000.0,((double)pp->xmax)/80000.0,((double)pp->ymin)/120000.0,((double)pp->ymax)/120000.0);
for(uint32_t c=0;c<pp->sub_count;c++) {
// AREA 는 TOWN 을 제외하고는 offset 보다는 index 가 좋겠음..
uint32_t cidx = pp->sub_offset / sizeof(_JA_AREA_PACKET);
JA_AREA_PACKET* cc = &_areas[cidx + c];
qInfo() << "\t" << QString::fromUtf16((ushort*)&_strings[cc->nameOffset]) << QString().sprintf("towns:%d bound: %.6f,%.6f,%.6f,%.6f",cc->sub_count,((double)cc->xmin)/80000.0,((double)cc->xmax)/80000.0,((double)cc->ymin)/120000.0,((double)cc->ymax)/120000.0);
for(uint32_t t=0;t<cc->sub_count;t++) {
uint32_t tidx = cc->sub_offset / sizeof(_JA_AREA_PACKET);
JA_AREA_PACKET* tt = &_areas[tidx + t];
qInfo() << "\t\t" << QString::fromUtf16((ushort*)&_strings[tt->nameOffset]) << QString().sprintf("offset:0x%X jibuns:%d bound: %.6f,%.6f,%.6f,%.6f",tt->sub_offset,tt->sub_count,((double)tt->xmin)/80000.0,((double)tt->xmax)/80000.0,((double)tt->ymin)/120000.0,((double)tt->ymax)/120000.0);
_debugTown(tt);
//break;
}
//break;
}
//break;
}
}
void FMAddress::_debugTown(JA_AREA_PACKET* town)
{
uint32_t bytes = _ABitsPackToByte(town->bits); // 1 지번당 바이트
const uint32_t xm = ((1 << town->bits[0])-1); // xmask
const uint32_t ym = ((1 << town->bits[1])-1); // ymask
const uint32_t a0m = ((1 << town->bits[2])-1); // a0mask
const uint32_t a1m = ((1 << town->bits[3])-1); // a1mask
// A0-A1-x-y shift
const uint32_t xs = town->bits[1] + town->bits[2] + town->bits[3];
const uint32_t ys = town->bits[2] + town->bits[3]; // a0
const uint32_t a0s = town->bits[3]; // a0
const uint32_t a1s = 0; // a1
uint8_t* buffer = (uint8_t*)malloc(bytes * town->sub_count); // 할당
uint64_t v; // 데이터 처리용
fseek(_file,town->sub_offset,SEEK_SET);
fread(buffer,bytes * town->sub_count,1,_file);
for(uint32_t j=0;j<town->sub_count;j++)
{
v = 0;
memcpy(&v,&buffer[bytes*j],bytes);
uint32_t vx = (v >> xs) & xm;
uint32_t vy = (v >> ys) & ym;
uint32_t va0 = (v >> a0s) & a0m;
uint32_t va1 = (v >> a1s) & a1m;
qInfo() << "\t\t\t" << QString().sprintf("#%d %d-%d %d,%d (%.6f,%.6f)",j+1,va0,va1,vx,vy,((double)(town->xmin + vx))/80000.0,((double)(town->ymin + vy))/120000.0);
}
free(buffer);
}
bool FMAddress::verify(QString src)
{
qInfo() << "START VERIFY:" << src << __FUNCTION__ << __LINE__;
QElapsedTimer et;
et.start();
QFile file(src);
if (!file.open(QIODevice::ReadOnly)) {
qInfo() << file.errorString();
return false;
}
#if (USE_ADDR_DB_TYPE == 1)
// 한줄 버리기
qInfo() << QString::fromStdString(file.readLine().toStdString());
#endif
int diffCount = 0;
int processCount = 0;
int lines = 0;
while (!file.atEnd())
{
QByteArray line = file.readLine();
lines++;
if(lines > 0 && lines % 100000 == 0) {
//qInfo() << lines;
}
QString str = QString::fromStdString(line.toStdString());
QStringList lst = str.split(",");
#if (USE_ADDR_DB_TYPE == 0)
QString pref = lst.at(1);
QString city = lst.at(2);
QString town = lst.at(3);
int a0 = lst.at(4).toInt();
int a1 = lst.at(5).toInt();
if (a1 > 1000) {
continue;
}
double lon = lst.at(6).toDouble();
double lat = lst.at(7).toDouble();
#elif (USE_ADDR_DB_TYPE == 1)
QString pref = lst.at(0);
QString city = lst.at(1);
QString town = lst.at(2);
// '青森県' and ADDR_2TH.SI_NAME = '三沢市'
if(town.isEmpty()) {
town = " ";
}
bool bok0, bok2;
int a0 = lst.at(3).toInt(&bok0);
int a1 = lst.at(4).toInt(&bok2);
if(bok0 == false || bok2 == false) {
continue;
}
double lon = lst.at(8).toDouble();
double lat = lst.at(7).toDouble();
#endif
wchar_t* pname = NULL;
wchar_t* cname = NULL;
wchar_t* tname = NULL;
int ra0 = -1;
int ra1 = -1;
int dist = -1;
processCount += 1;
if(!search(lon,lat,&pname,&cname,&tname,&ra0,&ra1,&dist,2) || sqrt((double)dist) > 20.0 || a0 != ra0 || a1 != ra1)
{
QString qpname = QString::fromUtf16((ushort*)pname);
QString qcname = QString::fromUtf16((ushort*)cname);
QString qtname = QString::fromUtf16((ushort*)tname);
if(abs((int)(lon*80000.0) - _found_x) > 10 ||
abs((int)(lat*120000.0) - _found_y) > 10
// pref != qpname ||
// city != qcname ||
// town != qtname
)
{
qInfo() << "NOT FOUND!!!" << str << qpname << qcname << qtname << "DIST:" << sqrt((double)dist) << "RA0-A1" << ra0 << ra1 << "LON/LAT" << QString().sprintf("%.6f,%.6f",((double)_found_x)/80000.0,((double)_found_y)/120000.0);
file.close();
return false;
}
double min = ((double)et.elapsed()) / 60000.0;
qInfo() << "DIFF COUNT:" << a0 << ra0 << a1 << ra1 << ++diffCount << "/" << processCount << QString().sprintf("%.1f min",min);
}
//qInfo() << "FOUND:" << str << QString::fromUtf16((ushort*)pname) << QString::fromUtf16((ushort*)cname) << QString::fromUtf16((ushort*)tname) << "DIST:" << sqrt((double)dist) << "RA0-A1" << ra0 << ra1;
}
file.close();
double min = ((double)et.elapsed()) / 60000.0;
qInfo() << "DONE:" << QString().sprintf("%.1f min",min) << __FUNCTION__ << __LINE__;
return true;
}
bool FMAddress::convert(QString src, QString target)
{
qInfo() << "START:" << src << __FUNCTION__ << __LINE__;
QElapsedTimer et;
et.start();
_currentPref = NULL;
_freeConvertData();
_prefs = (JA_AREA*)malloc(sizeof(_JA_AREA) * MAX_PREF_COUNT);
memset(_prefs,0,sizeof(_JA_AREA) * MAX_PREF_COUNT);
// CODE,PREF,CITY,CHOME,A0,A1,LON,LAT,CDATE
// 253851,11469174 -> 기존 DB ADDR_1TH,ADDR_2TH
// ADDR_1TH 의 경우 旭ヶ丘一丁目, 旭ヶ丘二丁目 등으로 각 丁目 별로 BUNJI 1개씩만 저장
// ADDR_2TH 의 경우 모든 BUNJI 포함하고 HO는 1개씩 저장
// 22467875 -> 신규 DB
// X=LON*80000, Y:LAT*120000
QFile file(src);
if (!file.open(QIODevice::ReadOnly)) {
qInfo() << file.errorString();
return false;
}
QString maxA0 = "";
QString maxA1 = "";
// 초기화
_currentPrefName = "";
_currentCityName = "";
_currentChomeName = "";
_header.pref_count = 0;
#if (PROCESS_TEST_CITY)
int townCount = 0; // 테스트용
#endif
#if (USE_ADDR_DB_TYPE == 1)
// 한줄 버리기
qInfo() << QString::fromStdString(file.readLine().toStdString());
#endif
int lines = 0;
while (!file.atEnd())
{
QByteArray line = file.readLine();
lines++;
if(lines > 0 && lines % 100000 == 0) {
//qInfo() << lines;
}
QString str = QString::fromStdString(line.toStdString());
QStringList lst = str.split(",");
#if (USE_ADDR_DB_TYPE == 0)
//QString code = lst.at(0);
QString pref = lst.at(1);
QString city = lst.at(2);
QString chome = lst.at(3);
int a0 = lst.at(4).toInt();
int a1 = lst.at(5).toInt();
if (a1 > 1000) {
//qInfo() << str;
continue;
}
#else
QString pref = lst.at(0);
QString city = lst.at(1);
QString chome = lst.at(2);
chome = chome.replace("\"","");
// '青森県' and ADDR_2TH.SI_NAME = '三沢市'
if(chome.isEmpty()) {
chome = " ";
}
bool bok0, bok2;
int a0 = lst.at(3).toInt(&bok0);
int a1 = lst.at(4).toInt(&bok2);
if(bok0 == false || bok2 == false) {
//qInfo() << "ERROR:" << str;
continue;
}
#endif
// if(lines >= 965764) {
// // 宮城県
// qInfo() << str << lines;
// }
// 東京都
// if(pref != QString::fromUtf8("\xe6\x9d\xb1\xe4\xba\xac\xe9\x83\xbd"))
// {
// continue;
// }
// 千代田
// if(lst.at(0) != QString("13101"))
// {
// continue;
// }
// 도도부현 변경시
if(_currentPrefName != pref) {
_currentPrefName = pref;
qInfo() << _currentPrefName;
_currentPref = &_prefs[_header.pref_count];
wcscpy((wchar_t*)&_currentPref->name[0],(wchar_t*)pref.utf16());
_currentCity = (JA_AREA*)malloc(sizeof(_JA_AREA) * MAX_CITY_COUNT);
memset(_currentCity,0,sizeof(_JA_AREA) * MAX_CITY_COUNT);
_currentPref->subAreas = _currentCity;
_header.pref_count += 1;
_currentPref->count = 0;
_currentCityName = "";
//qInfo() << "pref:" << pref;
}
// 시구군 변경시
if(_currentCityName != city) {
qInfo() << pref << city << lines;
_currentCity = &_currentPref->subAreas[_currentPref->count];
_currentPref->count += 1;
_currentCityName = city;
wcscpy((wchar_t*)&_currentCity->name[0],(wchar_t*)city.utf16());
_currentChome = (JA_AREA*)malloc(sizeof(_JA_AREA) * MAX_CHOME_COUNT);
memset(_currentChome,0,sizeof(_JA_AREA) * MAX_CHOME_COUNT);
_currentCity->subAreas = _currentChome;
_currentChomeName = "";
}
// 동변경시
if(_currentChomeName != chome) {
//qInfo() << pref << city << chome;
#if (PROCESS_TEST_CITY)
if(townCount >= 1) {
break;
}
townCount +=1;
#endif
// 기존 CHOME 데이터 크기대로 리사이즈
if(_currentChome != NULL) {
_resizeChome(_currentChome);
}
// current 변경
_currentChome = &_currentCity->subAreas[_currentCity->count];
_currentCity->count += 1; // 개수 추가
_currentChomeName = chome; // 명칭->저장
wcscpy((wchar_t*)&_currentChome->name[0],(wchar_t*)chome.utf16());
// 지번 생성 (개수를 모르니 그냥 처리)
_currentJibun = (JA_JIBUN*)malloc(sizeof(_JA_JIBUN) * MAX_JIBUN_COUNT);
memset(_currentJibun,0,sizeof(_JA_JIBUN) * MAX_JIBUN_COUNT);
_currentChome->subJibuns = _currentJibun;
//qInfo() << chome << _currentChome->count << __FUNCTION__ << __LINE__;
}
// if(_currentChome->count >= MAX_JIBUN_COUNT) {
// qInfo() << str << _currentChome->count << lines;
// break;
// }
// if(lines > 659415) {
// qInfo() << lines;
// }
// 모든 지번 처리
// CODE,PREF,CITY,CHOME,A0,A1,LON,LAT,CDATE
_currentJibun = &_currentChome->subJibuns[_currentChome->count];
_currentChome->count += 1; // 지번
_currentJibun->a0 = a0;
_currentJibun->a1 = a1;
// // X=LON*80000, Y:LAT*120000
#if (USE_ADDR_DB_TYPE == 0)
_currentJibun->x = (int)(lst.at(6).toDouble() * 80000.0);
_currentJibun->y = (int)(lst.at(7).toDouble() * 120000.0);
#else
_currentJibun->x = (int)(lst.at(8).toDouble() * 80000.0);
_currentJibun->y = (int)(lst.at(7).toDouble() * 120000.0);
#endif
if(_currentJibun->x < 0 || _currentJibun->y < 0)
{
qInfo() << "!!!!!!" << str;
}
// _maxLenPref: 4 _maxLenCity: 8 _maxLenChome: 12
_maxLenPref = qMax(pref.length(),_maxLenPref);
_maxLenCity = qMax(city.length(),_maxLenCity);
_maxLenChome = qMax(chome.length(),_maxLenChome);
bool bLog = false;
if(a0 > _maxA0) {
maxA0 = str;
bLog = true;
}
if (a1 > _maxA1) {
maxA1 = str;
bLog = true;
}
_maxA0 = qMax(a0,_maxA0);
_maxA1 = qMax(a1,_maxA1);
//const ushort* ut = pref.utf16();
//qInfo() << pref << pref.length() << QString::fromUtf16(pref.utf16()) << wcslen((const wchar_t*)ut);
if(bLog) {
//qInfo() << str;
}
// "27220,大阪府,箕面市,箕面四丁目,16,57555,135.474490149,34.832934236,20190327\n"
// OK
// if(code == "27146" &&
// chome == QString("\xe6\x96\xb0\xe9\x87\x91\xe5\xb2\xa1\xe7\x94\xba\xe4\xba\x94\xe4\xb8\x81") &&
// a0 == 7) {
// qInfo() << str;
// }
// OK
// if(code == "40231" &&
// chome == QString("\xe4\xbb\xb2\xe4\xb8\x80\xe4\xb8\x81\xe7\x9b\xae") &&
// a0 == 150) {
// qInfo() << str;
// }
// "27220,大阪府,箕面市,小野原西三丁目,9,1314,135.508661045,34.832548066,20190327\n" -> NG
// if(code == "27220" &&
// chome == QString("\xe5\xb0\x8f\xe9\x87\x8e\xe5\x8e\x9f\xe8\xa5\xbf\xe4\xb8\x89\xe4\xb8\x81\xe7\x9b\xae") &&
// a0 == 9) {
// qInfo() << str;
// }
// "15222,新潟?,上越市,安江二丁目,5,2000,138.264244772,37.170192031,20170804\n" -> NG
// if(code == "15222" &&
// chome == QString("\xe5\xae\x89\xe6\xb1\x9f\xe4\xba\x8c\xe4\xb8\x81\xe7\x9b\xae") &&
// a0 == 5) {
// qInfo() << str;
// }
}
// 마지막 TOWN 처리
if(_currentChome != NULL) {
_resizeChome(_currentChome);
}
//qInfo() << "_maxLenPref:" << _maxLenPref << "_maxLenCity:" << _maxLenCity << "_maxLenChome:" << _maxLenChome;
//qInfo() << "_maxA0:" << _maxA0 << "_maxA1:" << _maxA1;
//qInfo() << "maxA0 addr:" << maxA0;
//qInfo() << "maxA1 addr:" << maxA1;
//qInfo() << "REC NO.:" << lines;
uint32_t maxChome = 0;
uint32_t maxJibun = 0;
for(uint32_t p=0;p<_header.pref_count;p++) {
JA_AREA* pref = &_prefs[p];
//qInfo() << QString::fromUtf16(pref->name) << pref->count;
JA_AREA* citys = pref->subAreas;
for(uint32_t c=0;c<(uint32_t)pref->count;c++) {
JA_AREA* city = &citys[c];
//qInfo() << "\t\t" << QString::fromUtf16(city->name) << city->count;
maxChome = qMax(maxChome,(uint32_t)city->count);
JA_AREA* chomes = city->subAreas;
for(uint32_t t=0;t<(uint32_t)city->count;t++) {
JA_AREA* chome = &chomes[t];
maxJibun = qMax(maxJibun,(uint32_t)chome->count);
//qInfo() << "\t\t" << QString::fromUtf16(chome->name) << chome->count;
}
}
}
//qInfo() << "MAX_CHOME" << maxChome;
//qInfo() << "MAX_JIBUN" << maxJibun;
//qInfo() << __FUNCTION__ << __LINE__;
// 데이터 확인 + MIN MAX 처리 + 문자열 테이블 처리
_createStatCSV();
//qInfo() << __FUNCTION__ << __LINE__;
// 저장
_outFile = fopen(target.toLocal8Bit(),"wb");
// 데이터 저장
_saveData();
_freeConvertData();
fclose(_outFile);
double min = ((double)et.elapsed()) / 60000.0;
qInfo() << "DONE:" << QString().sprintf("%.1f min",min) << __FUNCTION__ << __LINE__;
return true;
}
void FMAddress::_updateBound(_JA_AREA* dest, _JA_AREA* src)
{
dest->xmin = qMin(dest->xmin,src->xmin);
dest->ymin = qMin(dest->ymin,src->ymin);
dest->xmax = qMax(dest->xmax,src->xmax);
dest->ymax = qMax(dest->ymax,src->ymax);
}
void FMAddress::_updateBoundP(_JA_AREA* dest, _JA_JIBUN* src)
{
dest->xmin = qMin(dest->xmin,src->x);
dest->ymin = qMin(dest->ymin,src->y);
dest->xmax = qMax(dest->xmax,src->x);
dest->ymax = qMax(dest->ymax,src->y);
dest->a0min = qMin(dest->a0min,(uint16_t)src->a0);
dest->a0max = qMax(dest->a0max,(uint16_t)src->a0);
dest->a1min = qMin(dest->a1min,(uint16_t)src->a1);
dest->a1max = qMax(dest->a1max,(uint16_t)src->a1);
}
void FMAddress::_initBound(_JA_AREA* dest)
{
dest->xmin = 160 * 80000;
dest->ymin = 50 * 120000;
dest->xmax = 0;
dest->ymax = 0;
// min,max
dest->a0min = 10000;
dest->a0max = 0;
dest->a1min = 10000;
dest->a1max = 0;
}
void FMAddress::_calculateBitPack(_JA_AREA * src) // , uint8_t* byte
{
// 32의 경우 5bit 만 사용하면 31까지만 표현가능하여 무조건 1씩 추가
int a0Bits = ceil(log((double)(src->a0max+1)) / log( 2.0 ));
int a1Bits = ceil(log((double)(src->a1max+1)) / log( 2.0 ));
int w = src->xmax - src->xmin;
int h = src->ymax - src->ymin;
int xBits = 0;
if(w > 0) {
xBits = ceil(log((double)(w+1)) / log( 2.0 ));
}
int yBits = 0;
if(h > 0) {
yBits = ceil(log((double)(h+1)) / log( 2.0 ));
}
src->bits[0] = xBits = qMax(xBits,4);
src->bits[1] =yBits = qMax(yBits,4);
src->bits[2] =a0Bits = qMax(a0Bits,4);
src->bits[3] =a1Bits = qMax(a1Bits,4);
//int bytes = ceil((double)(xBits + yBits + a0Bits + a1Bits)/8.0);
//*byte = (uint8_t)bytes;
//return QString().sprintf("%02d,%02d,%02d,%02d,%02d",bytes,xBits,yBits,a0Bits,a1Bits);
}
int _compareJIBUN(const void *j1, const void *j2)
{
if(((JA_JIBUN*)j1)->x == ((JA_JIBUN*)j2)->x)
{
return ((JA_JIBUN*)j1)->y - ((JA_JIBUN*)j2)->y;
}
return ((JA_JIBUN*)j1)->x - ((JA_JIBUN*)j2)->x;
}
// 지번 X 순으로 정렬
void FMAddress::_sortJIBUN(JA_AREA* chome)
{
qsort(chome->subJibuns,chome->count,sizeof(_JA_JIBUN),_compareJIBUN);
}
void FMAddress::_saveData()
{
// 헤더 저장
_writeOffset = 0;
fwrite(&_header,sizeof(_JA_HEADER),1,_outFile);
_writeOffset += sizeof(_JA_HEADER);
// 문자열 테이블 처리+저장
_saveStringTable();
//_header.prefOffset = _writeOffset;
// 도도부현 저장
_savePref();
qInfo() << "-----------------------------------------------------";
qInfo() << "HEADER : Bound:" << _header.xmin << _header.ymin << _header.xmax << _header.ymax;
qInfo() << "HEADER : Bound(Lon/Lat):" << QString().sprintf("%.6f,%.6f,%.6f,%.6f",(double)_header.xmin / 80000.0,(double)_header.ymin / 120000.0,(double)_header.xmax / 80000.0,(double)_header.ymax / 120000.0);
qInfo() << "HEADER : ID/DATE/VERSION:" << _header.header << _header.date << _header.version;
qInfo() << "HEADER : STRING SIZE:" << _header.stringSize;
qInfo() << "HEADER : PREF,COUNT:" << _header.pref_count;
qInfo() << "HEADER : CITY,COUNT:" << _header.city_count;
qInfo() << "HEADER : TOWN,COUNT:" << _header.town_count;
qInfo() << "HEADER : JIBUN,COUNT:" << _header.jibun_count;
uint32_t area_size = (_header.pref_count + _header.city_count + _header.town_count) * sizeof(_JA_AREA_PACKET);
qInfo() << "HEADER SIZE:" << sizeof(_JA_HEADER);
qInfo() << "STRING TABLE SIZE:" << _header.stringSize;
qInfo() << "AREA TABLE SIZE:" << area_size;
qInfo() << "JIBUN TOTAL BYTE:" << _totalJibunBytes;
qInfo() << "CURRENT FILE SIZE:" << sizeof(_JA_HEADER) + _header.stringSize + area_size + _totalJibunBytes; // 2,455,848, 2455850
}
bool _compareStringTable(const QString &k1, const QString &k2)
{
return FMAddress::_stringTable.value(k1) < FMAddress::_stringTable.value(k2);
}
void FMAddress::_saveStringTable()
{
QList<QString> ordered = _stringTable.keys();
qSort(ordered.begin(),ordered.end(),_compareStringTable);
uint32_t offset = 0;
// 정렬하고
foreach (QString key, ordered) {
offset = _stringTable.value(key);
//qInfo() << key << offset;
fseek(_outFile, sizeof(_JA_HEADER) + offset,SEEK_SET);
fwrite((wchar_t*)key.utf16(),key.length() * 2,1,_outFile);
}
wchar_t end = {0,};
fwrite(&end,2,1,_outFile); // 마지막 종료 문자
_writeOffset = sizeof(_JA_HEADER) + _header.stringSize; // 문자열 테이블 크기까지
}
void FMAddress::_copyPacket(JA_AREA_PACKET* dest, JA_AREA* src)
{
dest->nameOffset = src->nameOffset;
dest->sub_count = src->count;
dest->xmin = src->xmin;
dest->xmax = src->xmax;
dest->ymin = src->ymin;
dest->ymax = src->ymax;
dest->bits[0] = src->bits[0];
dest->bits[1] = src->bits[1];
dest->bits[2] = src->bits[2];
dest->bits[3] = src->bits[3];
}
bool FMAddress::_packJibun(JA_AREA* town,uint8_t* buffer,uint32_t* offset)
{
const uint32_t packetSize = sizeof(_JA_AREA_PACKET);
// 지번 파일 시작 옵셋
const uint32_t jStartOffset = sizeof(_JA_HEADER) + _header.stringSize + (_header.pref_count * packetSize) + (_header.city_count * packetSize) + (_header.town_count * packetSize);
uint32_t byte = _ABitsPackToByte(town->bits);// _bitPackToByte(town); // 레코드당 바이트 크기
if(byte > 8) {
qInfo() << "TOO MANY BYTES!!!!!!!!!!!!!!!!";
return false;
}
uint32_t off = *offset;
town->offset = (jStartOffset + off);
// mask
const uint32_t xm = ((1 << town->bits[0])-1); // xmask
const uint32_t ym = ((1 << town->bits[1])-1); // ymask
const uint32_t a0m = ((1 << town->bits[2])-1); // a0mask
const uint32_t a1m = ((1 << town->bits[3])-1); // a1mask
// A0-A1-x-y shift
const uint32_t xs = town->bits[1] + town->bits[2] + town->bits[3];
const uint32_t ys = town->bits[2] + town->bits[3]; // a0
const uint32_t a0s = town->bits[3]; // a0
const uint32_t a1s = 0; // a1
//qInfo() << "---------------------------------------------------";
//qInfo() << "PACK:" << QString::fromUtf16(town->name) << "BYTE:" << byte << "BITS:" << town->bits[0] << town->bits[1] << town->bits[2] << town->bits[3];
//qInfo() << "mask:" << xm << ym << a0m << a1m;
//qInfo() << "shifts:" << xs << ys << a0s << a1s;
for(int j = 0;j<town->count;j++)
{
uint8_t *pj = &buffer[off];
JA_JIBUN* item = &town->subJibuns[j];
uint32_t x = (uint32_t)(item->x - town->xmin);
uint32_t y = (uint32_t)(item->y - town->ymin);
uint32_t a0 = (uint32_t)item->a0;
uint32_t a1 = (uint32_t)item->a1;
uint64_t pp = ((uint64_t)x << xs) | ((uint64_t)y << ys) | (a0 << a0s) | (a1 << a1s);
uint8_t* pt = (uint8_t*)&pp;
// 1244345 -> 235 252 27 0 0 0 0 0
//qInfo() << "PTB:" << pt[0] << pt[1] << pt[2] << pt[3] << pt[4] << pt[5] << pt[6] << pt[7];
memcpy(pj,pt,byte);
off += byte;
//qInfo() << "XY:" << x << y << "J:" << a0 << a1 << "P64:" << pp << "OFFSET:" << off;
// unpack/verify
uint8_t vb[8] = {0,};
memcpy(vb,pt,byte);
uint64_t *vp = (uint64_t*)vb;
uint32_t vx = ((*vp) >> xs) & xm;
uint32_t vy = ((*vp) >> ys) & ym;
uint32_t va0 = ((*vp) >> a0s) & a0m;
uint32_t va1 = ((*vp) >> a1s) & a1m;
if(vx != x || vy != y || va0 != a0 || va1 != a1) {
qInfo() << "PACK:" << QString::fromUtf16(town->name) << "BYTE:" << byte << "BITS:" << town->bits[0] << town->bits[1] << town->bits[2] << town->bits[3];
qInfo() << "mask:" << xm << ym << a0m << a1m;
qInfo() << "shifts:" << xs << ys << a0s << a1s;
qInfo() << "PTB:" << pt[0] << pt[1] << pt[2] << pt[3] << pt[4] << pt[5] << pt[6] << pt[7];
qInfo() << "XY:" << x << y << "J:" << a0 << a1 << "P64:" << pp << "OFFSET:" << off;
qInfo() << "!!!!!!! ERROR VXY:" << vx << vy << "J:" << va0 << va1;
return false;
}
}
*offset = off;
return true;
}
// OFFSET 생성 + 데이터 저장
void FMAddress::_savePref()
{
const uint32_t packetSize = sizeof(_JA_AREA_PACKET);
// 별도로 로딩하여 PREF PACKET = 0 부터 시작해야함
const uint32_t cityStartOffset = (_header.pref_count * packetSize);
const uint32_t townStartOffset = cityStartOffset + (_header.city_count * packetSize);
// 지번은 파일에서 로딩하여 실제 파일옵셋 저장
uint8_t *pJibun = (uint8_t*)malloc(_totalJibunBytes);
memset(pJibun,0,_totalJibunBytes);
//uint32_t totalArea = _header.pref_count + countCity + countTown;
JA_AREA_PACKET* plist = (JA_AREA_PACKET*)malloc(_header.pref_count * packetSize);
memset(plist,0,_header.pref_count * packetSize);
JA_AREA_PACKET* clist = (JA_AREA_PACKET*)malloc(_header.city_count * packetSize);
memset(clist,0,_header.city_count * packetSize);
JA_AREA_PACKET* tlist = (JA_AREA_PACKET*)malloc(_header.town_count * packetSize);
memset(tlist,0,_header.town_count * packetSize);
//int pIndex = 0; == p
int cIndex = 0;
int tIndex = 0;
uint32_t jOffset = 0;
// HEADER + STRING TABLE + PREFS.... + CITYS.... + TOWNS.... + JIBUNS....
for(uint32_t p=0;p<_header.pref_count;p++) { // 도도부현
JA_AREA* pp = &_prefs[p];
JA_AREA_PACKET* ppp = &plist[p];
_copyPacket(ppp,pp);
ppp->sub_offset = cityStartOffset + (cIndex * packetSize);
for(uint32_t c=0;c<(uint32_t)pp->count;c++) { // 시군구
JA_AREA* cc = &pp->subAreas[c];
JA_AREA_PACKET* ccc = &clist[cIndex++];
_copyPacket(ccc,cc);
ccc->sub_offset = townStartOffset + (tIndex * packetSize);
for(uint32_t t=0;t<(uint32_t)cc->count;t++) { // 동읍면
JA_AREA* tt = &cc->subAreas[t];
JA_AREA_PACKET* ttt = &tlist[tIndex++];
//ttt->sub_offset = 0;//jibunStartOffset;
_copyPacket(ttt,tt); // 데이터 저장
if(_packJibun(tt,pJibun,&jOffset) == false)
{
qInfo() << "PACK ERROR:" << QString::fromUtf16((ushort*)pp->name) << QString::fromUtf16((ushort*)cc->name) << QString::fromUtf16((ushort*)tt->name);
free(plist);
free(clist);
free(tlist);
free(pJibun);
return;
}
ttt->sub_offset = tt->offset;
// for(uint32_t j=0;j<(uint32_t)tt->count;j++) { // 번지 BYTE PACKET 생성
// //JA_JIBUN* jj = &((JA_JIBUN*)tt->offset)[j];
// // jOffset
// }
}
//tIndex += cc->count;
}
//cIndex += pp->count;
}
//uint32_t offset = _writeOffset; // 현재 WRITE OFFSET 에서 시작 (HEADER + STRING TABLE)
fwrite(plist,packetSize,_header.pref_count,_outFile);
fwrite(clist,packetSize,_header.city_count,_outFile);
fwrite(tlist,packetSize,_header.town_count,_outFile);
fwrite(pJibun,_totalJibunBytes,1,_outFile);
free(plist);
free(clist);
free(tlist);
free(pJibun);
}
//int FMAddress::_bitPackToByte(_JA_AREA * src)
//{
// return ceil((double)(src->bits[0] + src->bits[1] + src->bits[2] + src->bits[3])/8.0);
//}
// 중복 문자 필터 + 옵셋 저장
void FMAddress::_setStringOffset(JA_AREA* area,QString name)
{
_totalStringBufferLength += (MAX_JA_AREA_NAME_LEN * 2);
_totalStringLength += (name.length() * 2);
if(!_stringTable.contains(name)) {
area->nameOffset = _currentStringOffset;
_stringTable.insert(name,area->nameOffset); // HEADER +
_currentStringOffset += ((name.length()+1) * 2);
}
else {
area->nameOffset = _stringTable.value(name,0);
}
}
void FMAddress::_createStatCSV()
{
_totalStringBufferLength = 0;
_totalStringLength = 0;
_currentStringOffset = 0;
_header.xmin = 160 * 80000;
_header.ymin = 50 * 120000;
_header.xmax = 0;
_header.ymax = 0;
_header.city_count = 0;
_header.town_count = 0;
_header.jibun_count = 0;
// LOOP
for(uint32_t p=0;p<_header.pref_count;p++) { // 도도부현
JA_AREA* pp = &_prefs[p];
// qInfo() << QString::fromUtf16(pp->name);
_initBound(pp);
_setStringOffset(pp,QString::fromUtf16(pp->name));
_header.city_count += pp->count; // 전체 시구군 개수
for(uint32_t c=0;c<(uint32_t)pp->count;c++) { // 시구군
JA_AREA* cc = &pp->subAreas[c];
//qInfo() << QString::fromUtf16(cc->name) << cc->count << __FUNCTION__ << __LINE__;;
_setStringOffset(cc,QString::fromUtf16(cc->name));
_header.town_count += cc->count; // 전체 TOWN 개수
_initBound(cc);
for(uint32_t t=0;t<(uint32_t)cc->count;t++) { // 동읍면
JA_AREA* tt = &cc->subAreas[t];
_setStringOffset(tt,QString::fromUtf16(tt->name));
_header.jibun_count += tt->count; // 전체 지번 개수
// qInfo() << QString::fromUtf16(tt->name) << tt->count << __FUNCTION__ << __LINE__;
// 丁目 로 끝나지 않는 TOWN 도 많음..
_initBound(tt);
for(uint32_t j=0;j<(uint32_t)tt->count;j++) { // 번지
JA_JIBUN* jj = &tt->subJibuns[j];
_updateBoundP(tt,jj);
}
_updateBound(cc,tt);
_sortJIBUN(tt); // 지번을 X순으로 정렬
_calculateBitPack(tt); // 지번 데이터 BIT PACK 계산
}
_updateBound(pp,cc);
}
_header.xmin = qMin(_header.xmin,pp->xmin);
_header.ymin = qMin(_header.ymin,pp->ymin);
_header.xmax = qMax(_header.xmax,pp->xmax);
_header.ymax = qMax(_header.ymax,pp->ymax);
}
//qInfo() << __FUNCTION__ << __LINE__;
// LOOP 2 (CSV 생성)
QString dest = "C://home//roadmovie//script//jpaddr//report.csv";
QFile outFile( dest );
outFile.open( QIODevice::WriteOnly | QIODevice::Text);
QTextStream str( &outFile );
str.setCodec("UTF-16LE");
str.setGenerateByteOrderMark(true);
str << "DO,SI,DONG,COUND,XMIN,YMIN,XMAX,YMAX,BYTE,XB,YB,A0B,A1B\n";
_totalJibunBytes = 0;
for(uint32_t p=0;p<_header.pref_count;p++) { // 도도부현
JA_AREA* pp = &_prefs[p];
str << QString::fromUtf16(pp->name) << ",__,__," << QString::number(pp->count) << ",";
str << QString::number(pp->xmin) << "," << QString::number(pp->ymin) << "," << QString::number(pp->xmax) << "," << QString::number(pp->ymax) << "\n";
for(uint32_t c=0;c<(uint32_t)pp->count;c++) { // 시군구
JA_AREA* cc = &pp->subAreas[c];
str << QString::fromUtf16(pp->name) << "," << QString::fromUtf16(cc->name) << ",__," << QString::number(cc->count) << ",";
str << QString::number(cc->xmin) << "," << QString::number(cc->ymin) << "," << QString::number(cc->xmax) << "," << QString::number(cc->ymax) << "\n";
for(uint32_t t=0;t<(uint32_t)cc->count;t++) { // 동읍면
JA_AREA* tt = &cc->subAreas[t];
str << QString::fromUtf16(pp->name) << "," << QString::fromUtf16(cc->name) << "," << QString::fromUtf16(tt->name) << "," << QString::number(tt->count) << "," ;
str << QString::number(tt->xmin) << "," << QString::number(tt->ymin) << "," << QString::number(tt->xmax) << "," << QString::number(tt->ymax) << ",";
// _bitPackToByte(tt)
_totalJibunBytes += ( _ABitsPackToByte(tt->bits) * tt->count);
//uint8_t bt = 0;
//str << _calculateBitPack(tt,&bt) << "\n";
//totalByte += ((tt->bits[0] + tt->bits[1] + tt->bits[2] + tt->bits[0]) * tt->count);
// for(int j=0;j<tt->count;j++) { // 번지
// JA_JIBUN* jj = &((JA_JIBUN*)tt->offset)[j];
// }
}
}
}
//qInfo() << __FUNCTION__ << __LINE__;
// 현,개수
// -> 시,개수
// -> 동,개수,xmin,ymin,xmax,ymax
outFile.close();
qInfo() << "TOTAL JIBUN BYTES:" << _totalJibunBytes;
qInfo() << "STRINGS:" << _totalStringLength << _currentStringOffset << _totalStringBufferLength;
_header.stringSize = _currentStringOffset;
}
void FMAddress::_resizeChome(JA_AREA* chome)
{
//qInfo() << QString::fromUtf16(chome->name) << chome->count << __FUNCTION__ << __LINE__;
if(chome->count > 0) {
JA_JIBUN* nj = (JA_JIBUN*)malloc(sizeof(_JA_JIBUN) * chome->count);
memcpy(nj,chome->subJibuns,sizeof(_JA_JIBUN) * chome->count);
free(chome->subJibuns);
chome->subJibuns = nj;
}
}
void FMAddress::_freeConvertData() {
if(_prefs != NULL) {
for(uint32_t p=0;p<_header.pref_count;p++) {
JA_AREA* pref = &_prefs[p];
if (pref->count > 0) {
JA_AREA* citys = pref->subAreas;
for(uint32_t c=0;c<(uint32_t)pref->count;c++)
{
JA_AREA* city = &citys[c];
if (city->count > 0) {
JA_AREA* chomes = city->subAreas;
for(uint32_t t=0;t<(uint32_t)city->count;t++)
{
JA_AREA* chome = &chomes[t];
if(chome->count > 0)
{
free(chome->subJibuns);
}
}
free(chomes);
}
}
free(citys);
}
}
free(_prefs);
_prefs = NULL;
}
_currentJibun = NULL;
_currentPref = NULL;
_currentCity = NULL;
_currentChome = NULL;
}
#endif // USE_JP_ADDRESS_TOOL
#endif // USE_JP_ADDRESS