shjung 11 месяцев назад
Родитель
Сommit
e40a92fb37

+ 47 - 0
src/main/java/com/sig/todp/server/dto/TTod.java

@@ -0,0 +1,47 @@
+package com.sig.todp.server.dto;
+
+public class TTod {
+
+    public static final int ARING      = 0;
+    public static final int BRING      = 1;
+    public static final int MAX_RINGS  = 2;
+    public static final int MAX_PHASES = 8;
+    public static final int MAX_TRANS  = 5;
+    public static final int NEGATIVE   = 0;
+    public static final int POSITIVE   = 1;
+
+    public static final int NO_TRANS_NEEDED  = 0;       /* 전이 불필요 */
+    public static final int EXCEED_MAXTRANS  = -1;      /* 전이 횟수 초과 */
+    public static final int CYCLE_LENGTH_ERR = -2;      /* 목표 주기 계산 에러 */
+
+    public int[][] S; // Split (seconds), 듀얼링
+    public int O; // Offset (seconds)
+    public int hour; // 신호(주기) 시작 시각
+    public int min;
+    public int sec;
+
+    public TTod() {
+        this.S = new int[TTod.MAX_RINGS][TTod.MAX_PHASES];
+        this.O = 0;
+        this.hour = 0;
+        this.min = 0;
+        this.sec = 0;
+    }
+
+    public void copy(TTod in) {
+        for (int ii = 0; ii < TTod.MAX_RINGS; ii++) {
+            for (int jj = 0; jj < TTod.MAX_PHASES; jj++) {
+                this.S[ii][jj] = in.S[ii][jj];
+            }
+        }
+    }
+
+    public int sumPhase(int ring) {
+        return (this.S[ring][0]+this.S[ring][1]+this.S[ring][2]+this.S[ring][3]+this.S[ring][4]+this.S[ring][5]+this.S[ring][6]+this.S[ring][7]);
+    }
+    public void setPhase(int ring, int value) {
+        for (int ii = 0; ii < MAX_PHASES; ii++) {
+            this.S[ring][ii] = value;
+        }
+    }
+}

+ 38 - 0
src/main/java/com/sig/todp/server/dto/TTransition.java

@@ -0,0 +1,38 @@
+package com.sig.todp.server.dto;
+
+public class TTransition {
+    public TTod tPlan;             // 목표 주기 신호 계획
+    public int[][] minG;           // 최소 청시간
+    public int[][] maxG;           // 최대 청시간
+    public int phaseCnt;           // 현시 수
+    public int mainPhaseInx;       // 주현시 인덱스 (0..7)
+    public int progressionRing;    // 연동 방향 (0=ARING, 1=BRING)
+    public int transDir;           // 전이 방향 (1=POSITIVE, 2=NEGATIVE)
+    public int acceptableDelta;    // 전이 없이 주기 단순 보정 허용 델타값 (가급적 큰 것이 좋음, 10초 정도 까지도)
+    public int maxTransition;      // 허용 가능한 최대 전이 주기 수
+    public int keepPreInterval;    // 전이 주기 분할 시 선행 현시 길이 고정 (주현시 선행 현시를 길이 고정)
+    public int minimumOverlap;     // 최소 동시 신호 유지 시간
+    public byte dual;              // 듀얼 현시 운영 지정 (LSB: 1=1현시 듀얼)
+
+    public TTransition() {
+        this.tPlan = new TTod();
+        this.minG = new int[TTod.MAX_RINGS][TTod.MAX_PHASES];
+        this.maxG = new int[TTod.MAX_RINGS][TTod.MAX_PHASES];
+        this.phaseCnt = 0;
+        this.mainPhaseInx = 0;
+        this.progressionRing = 0;
+        this.transDir = 0;
+        this.acceptableDelta = 0;
+        this.maxTransition = 0;
+        this.keepPreInterval = 0;
+        this.minimumOverlap = 0;
+        this.dual = 0;
+    }
+    
+    public int sumPhaseMin(int ring) {
+        return (this.minG[ring][0]+this.minG[ring][1]+this.minG[ring][2]+this.minG[ring][3]+this.minG[ring][4]+this.minG[ring][5]+this.minG[ring][6]+this.minG[ring][7]);
+    }
+    public int sumPhaseMax(int ring) {
+        return (this.maxG[ring][0]+this.maxG[ring][1]+this.maxG[ring][2]+this.maxG[ring][3]+this.maxG[ring][4]+this.maxG[ring][5]+this.maxG[ring][6]+this.maxG[ring][7]);
+    }
+}

+ 343 - 0
src/main/java/com/sig/todp/server/dto/TodUtils.java

@@ -0,0 +1,343 @@
+package com.sig.todp.server.dto;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+public class TodUtils {
+
+    /*
+     ***************************************************************************************************
+     * FUNCTION NAME : CheckDirection
+     * DESCRIPTION   : 전이 방향 옵션에 따라 이번에 전이가 완료되는지를 검사, 1이 리턴되면 이번에 전이 완료,
+     * NOTES         :
+     ***************************************************************************************************/
+    public static boolean checkDirection(int dir, int transCycle, int tCycle) {
+        if (dir == TTod.NEGATIVE) {
+            // 음의 방향 전이인 경우, trans Length 가 83%와 100% 사이 이면 전이 완료
+            if (transCycle >= (tCycle*0.83) && transCycle <= tCycle)
+                return true;
+            else
+                return false;
+        }
+        else {
+            // 양의 방향 전이인 겨우, transLength 100~134 사이 이면 이전에 전이 완료
+            if (transCycle >= tCycle && transCycle <= (tCycle*1.34))
+                return true;
+            else
+                return false;
+        }
+    }
+
+    /*
+     ***************************************************************************************************
+     * FUNCTION NAME : StretchSplit
+     * DESCRIPTION   : 전이중 임시주기 길이를 TOD대로 늘려 맞춤
+     * NOTES         :
+     *            pt : 전이계획을 저장할 메모리
+     *    transCycle : 전이 임시 주기길이
+     *        tCycle : 목표 주기 길이
+     *           trn : 목표 주기에 대한 TOD 정보 구조체
+     *         반환값:  정상: 1,
+     *                  에러: -1 (목표주기가 0값임, Division by Zero)
+     ***************************************************************************************************/
+    public static int stretchSplit(TTod pt, int transCycle, int tCycle, TTransition trn) {
+        /* 주현시가 1현시가 아니고, 선행현시 길이 고정이면, 미리 할당 후 */
+        int transLen[] = new int[TTod.MAX_RINGS];            /* 각 링별 주기길이 */
+        int tCycleLen[] = new int[TTod.MAX_RINGS];           /* 각 링별 목표 주기길이 */
+        int sum[] = new int[TTod.MAX_RINGS];                 /* 스플릿팅 한 후 계산된 현시의 합 */
+        int startPhase = 0;         /* 고정할당 빼고 자동맞춤 시작 현시 */
+        int ring;                   /* 링 카운터 변수 */
+        int phase;                  /* 현시인덱스 카운터변수 */
+        int marginTime;             /* 현시합(sum)과 transeCycle와의 편차 */
+        int diff;                   /* A링 B링 현시 배리어 시간차 */
+        int comp;
+
+        if (tCycle <= 0) {
+            return -1;
+        }
+
+        /* 전이/목표 주기값을 각 링별 주기값으로 적용 */
+        transLen[TTod.ARING] = transCycle;
+        transLen[TTod.BRING] = transCycle;
+        tCycleLen[TTod.ARING] = tCycle;
+        tCycleLen[TTod.BRING] = tCycle;
+
+        /* 현시 셀 제로 초기화(주의!! SetPhase 매크로는 8현시 고정) */
+        pt.setPhase(TTod.ARING, 0);
+        pt.setPhase(TTod.BRING, 0);
+
+        /* 전이중 연동 확보를 위해 주현시 앞 현시들을 계획값으로 고정하는 경우 */
+        if (trn.keepPreInterval == 1 && trn.mainPhaseInx > 0) {
+            for (ring = TTod.ARING; ring <= TTod.BRING; ring++) {
+                for (phase = 0; phase < trn.mainPhaseInx; phase++) {
+                    pt.S[ring][phase] = trn.tPlan.S[ring][phase];      /* 선행 고정현시 할당 */
+                    transLen[ring]    -= pt.S[ring][phase];            /* 임시주기에서 고정현시제외 */
+                    tCycleLen[ring]   -= pt.S[ring][phase];            /* 목표주기에서도 제외 */
+                }
+            }
+            startPhase = trn.mainPhaseInx;
+        }
+
+        /* 나머지 현시들을 계획 현시 비율료 늘려 맞춤 */
+        for (ring = TTod.ARING; ring <= TTod.BRING; ring++) {
+            sum[ring] = 0;
+
+            /* Splitting intervals */
+            for (phase = startPhase; phase < trn.phaseCnt; phase++) {
+                /* 계획 현시와 주기값 비율로 전이주기량을 나누어 줌 */
+                pt.S[ring][phase] = (int) (transLen[ring] * (trn.tPlan.S[ring][phase]/(float)tCycleLen[ring])+0.5);
+
+                /* 최소청시간 위배 체크 */
+                if (pt.S[ring][phase] < trn.minG[ring][phase]) {
+                    pt.S[ring][phase] = trn.minG[ring][phase];
+                }
+
+                /* 최대청시간 위배 체크 */
+                if (pt.S[ring][phase] > trn.maxG[ring][phase]) {
+                    pt.S[ring][phase] = trn.maxG[ring][phase];
+                }
+
+                /* 최종할당 현시시간의 합 계산 */
+                sum[ring] += pt.S[ring][phase];
+            }
+
+            /* 짜투리 시간 계산 */
+            marginTime = sum[ring]-transLen[ring];
+
+            if (marginTime > 0) {
+                /* 짜투리 시간이 남으면 마지막 현시에서 제거 */
+                pt.S[ring][trn.phaseCnt-1] -= marginTime;
+            }
+            else {
+                /* 짜투리 시간이 모자라면 주현시에 할당 */
+                pt.S[ring][trn.mainPhaseInx] += -marginTime;
+            }
+
+            /* 이후 현시값을 0값으로 처리 */
+            for (; phase < TTod.MAX_PHASES; phase++) {
+                pt.S[ring][phase] = 0;
+            }
+        }
+
+        /* 듀얼현시지정 및 최소동시신호시간 검사 */
+        sum[TTod.ARING] = 0;
+        sum[TTod.BRING] = 0;
+
+//        trn.dual |= 0x01 < (trn.phaseCnt-1); /* 마지막 현시는 강제로 싱글배리어로 처리 */
+        trn.dual |= (byte) (0x01 << (trn.phaseCnt-1)); /* 마지막 현시는 강제로 싱글배리어로 처리 */
+        for (phase = 0; phase < trn.phaseCnt; phase++) {
+            sum[TTod.ARING] += pt.S[TTod.ARING][phase];
+            sum[TTod.BRING] += pt.S[TTod.BRING][phase];
+
+            /* A링과 B링의 배리어 시간 차를 절대값 으로 구함 */
+            diff = sum[TTod.ARING] - sum[TTod.BRING];
+            if (diff < 0) {
+                diff *= -1;
+            }
+
+            /* 듀얼이 허용되고 최소동시시간 조건이 만족하면 다음현시검사로 진행 */
+            comp = trn.dual & (byte)(0x01 << phase);
+            if (comp != 0 && diff >= trn.minimumOverlap) {
+                continue;
+            }
+            /* 싱글현시이고 현시가 동시에 끝나면 다음현시검사로 진행 */
+            if (sum[TTod.ARING] == sum[TTod.BRING]) {
+                continue;
+            }
+
+            /* 동시신호조건이 안되거나 싱글인데 다르게 끝나는경우, 강제 조정 */
+            diff = (int) (0.5 + diff / 2.0);     /* 시간 차의 반을 구함 */
+            if (sum[TTod.ARING] < sum[TTod.BRING]) {
+                /* A링이 빠르면 A링반늘리고, B링 반줄임 */
+                pt.S[TTod.ARING][phase] += diff; sum[TTod.ARING] += diff;
+                pt.S[TTod.BRING][phase] -= diff; sum[TTod.BRING] -= diff;
+            }
+            else {
+                /* 반대면, A링 반줄이고 B링 반늘림 */
+                pt.S[TTod.ARING][phase] -= diff; sum[TTod.ARING] -= diff;
+                pt.S[TTod.BRING][phase] += diff; sum[TTod.BRING] += diff;
+            }
+        }
+
+        /* 합 검사 후 편차만큼 주현시에 인가(주의!! SetPhase 매크로는 8현시 고정) */
+        pt.S[TTod.ARING][trn.mainPhaseInx] += transCycle - pt.sumPhase(TTod.ARING);
+        pt.S[TTod.BRING][trn.mainPhaseInx] += transCycle - pt.sumPhase(TTod.BRING);
+        return 1;
+    }
+
+    /*
+     ***************************************************************************************************
+     * FUNCTION NAME : makeTransition
+     * DESCRIPTION   : 전이계획 수립 함수
+     * NOTES         :
+     *         input : 현시 설정 입력데이터 구조체
+     *           out : 전이 계획 저장할 tTod[5] 구조체 배열
+     *        aclock : 현재 시각(또는 계산 시점의 0:0:0 기준 초단위 누적 시간값)
+     *         반환값:  정상: 0=전이 필요 없음, 1~3 : 전이 횟수,
+     *                  에러: -1 : 최대 전이 횟수 초과, -2 : 목표주기가 0값임
+     ***************************************************************************************************/
+    public static int makeTransition(TTransition input, List<TTod> out, int aClock) {
+        int tCycle;             /* 전이 목표 주기 길이 */
+        int minCycle;           /* 최소주기길이(최소녹색시간의 합) */
+        int maxCycle;           /* 최대주기길이(최대녹색시간의 합) */
+        int sumA, sumB;         /* 링별 현시합을 계산하여 주기를 계산하기위한 임시 정수 변수 */
+        int r;                  /* 링을 의미하는 루프계산 카운터 */
+        int p;                  /* 현시를 의미하는 루프계산 카운터 */
+        int delta ;             /* 0시부터 목표주기로 진행되었을 경우 현재 잔여 주기 카운터 */
+        int adjDelta;           /* 주현시 개념을 반영하여 보정된 delta */
+        int tCnt;               /* 총 전이주기 수를 카운트하기 위한 정수 */
+        int preInterval = 0;    /* 주현시 이전 선행현시의 합 */
+        int transLength;        /* delta 만큼 진행된 tCycle 의 나머지 시간 */
+
+        int deltaSum;
+        int deltaPerPhase;
+        int transResult;
+
+
+        /* 1. 현재 시각 얻기 */
+        if (aClock == 0) {
+            LocalDateTime now = LocalDateTime.now();
+            aClock = now.getHour() * 3600 + now.getMinute() * 60 + now.getSecond();
+        }
+
+        /* 2. 사용하지 않는 현시 자료 초기화 */
+        for (p = input.phaseCnt; p < TTod.MAX_PHASES; p++) {
+            for (r = 0; r < 2; r++) {
+                input.tPlan.S[r][p] = input.minG[r][p] = input.maxG[r][p] = 0;
+            }
+        }
+
+        /* 3. 목표주기길이 계산(최대값) */
+        sumA = input.tPlan.sumPhase(TTod.ARING);
+        sumB = input.tPlan.sumPhase(TTod.BRING);
+        tCycle = Math.max(sumA, sumB);
+
+        /* 4. 최소주기길이 계산(최대값) */
+        sumA = input.sumPhaseMin(TTod.ARING);
+        sumB = input.sumPhaseMin(TTod.BRING);
+        minCycle = Math.max(sumA, sumB);
+
+        /* 5. 최대주기길이 계산(최대값) */
+        sumA = input.sumPhaseMax(TTod.ARING);
+        sumB = input.sumPhaseMax(TTod.BRING);
+        maxCycle = Math.max(sumA, sumB);
+
+        /* 6. 선행 현시 인터벌  계산 */
+        for (p = 0; p < input.mainPhaseInx; p++) {
+            preInterval += input.tPlan.S[input.progressionRing][p];
+        }
+
+        /* 7. 전이 판단 및 장래 주기 계획 생성 */
+        for (tCnt = 0; tCnt < input.maxTransition && tCnt < TTod.MAX_TRANS; tCnt++) {
+            /* 7-1. delta 계산: 0시|-O-+----C----+dddT  ddd= (T-O)%C */
+            delta = (aClock-input.tPlan.O) % tCycle;
+
+            /* 7-2. 주현시 보정 delta 계산 */
+            adjDelta = (delta + preInterval) % tCycle;
+
+            /* 7-3. 첫 전이주기 시작 시각 저장 */
+            out.get(tCnt).hour =  aClock/3600;
+            out.get(tCnt).min  = (aClock%3600)/60;
+            out.get(tCnt).sec  =  aClock%60;
+
+            /* 7-4. 전이 여부 결정, 허용 시간(3~5)내 차이는 주현시 단순 보정, 비 전이 처리 */
+            if ( adjDelta           <= input.acceptableDelta ||   /* 0~3초 시간 지난 상황 */
+                (tCycle - adjDelta) <= input.acceptableDelta )    /* 주기에서 0~3초 모자랄때 */
+            {
+                /* 단순보정이므로 TOD 플랜을 일단 복사한 후 보정 */
+                out.get(tCnt).copy(input.tPlan);
+
+                /* 목표 주기 에서 0~3초 모자 라면 음수로 변환 하여 주기를 줄인다. */
+                if ((tCycle - adjDelta) <= input.acceptableDelta) {
+                    adjDelta -= tCycle;
+                }
+
+                /* 주기 길이 변화가 없으면 전이가 아님 */
+                if (adjDelta == 0) {
+                    transResult = tCnt;   /* 전이 횟수 */
+                    return transResult;
+                }
+
+                /* 단순보정으로 줄어든 주기길이가 최소주기보다 길어야 함 */
+                if ((tCycle+adjDelta) >= minCycle && (tCycle+adjDelta) <= maxCycle) {
+                    if (adjDelta < 0) {
+                        /* 주기가 증가 되는 델타 값은 주 현시에 부여 */
+                        out.get(tCnt).S[TTod.ARING][input.mainPhaseInx] -= adjDelta;
+                        out.get(tCnt).S[TTod.BRING][input.mainPhaseInx] -= adjDelta;
+                    }
+                    else {
+                        /* 감소 델타 값은 타 현시 에서 빼줌 */
+                        deltaPerPhase = (int) (0.5 + (float)adjDelta / (input.phaseCnt - 1));
+
+                        if (deltaPerPhase < 1) {
+                            /* 1초씩도 안 돌아 가면 마지막 현시서 제함 */
+                            out.get(tCnt).S[TTod.ARING][input.phaseCnt-1] -= adjDelta;
+                            out.get(tCnt).S[TTod.BRING][input.phaseCnt-1] -= adjDelta;
+                        }
+                        else {
+                            /* 배분해 주고, 잔차는 마지막 현시 에서 한꺼번에 처리 */
+                            for (deltaSum = 0, p = 0; p < input.phaseCnt-1; p++) {
+                                if (p==input.mainPhaseInx)
+                                    continue;
+
+                                out.get(tCnt).S[TTod.ARING][p] -= deltaPerPhase;
+                                out.get(tCnt).S[TTod.BRING][p] -= deltaPerPhase;
+                                deltaSum += deltaPerPhase;
+                            }
+
+                            out.get(tCnt).S[TTod.ARING][input.phaseCnt-1] -= adjDelta-deltaSum;
+                            out.get(tCnt).S[TTod.BRING][input.phaseCnt-1] -= adjDelta-deltaSum;
+                        }
+
+                    }
+                    transResult = tCnt;   /* 전이 횟수 */
+                    return transResult;
+                }
+            }
+
+            /* 10. 이번 전이주기가 너무 작으면 1주기보다 긴 값으로 전이주기 조정 */
+            transLength = tCycle - adjDelta;
+            if (transLength < minCycle)
+            {
+                transLength += tCycle;
+            }
+
+            /* 11. transLength가 목표주기의 83% ~ 133% 사이이면 이번에 전이 완료 */
+            if (checkDirection(input.transDir, transLength, tCycle)) {
+                if (stretchSplit(out.get(tCnt), transLength, tCycle, input) < 0) {
+                    transResult = TTod.CYCLE_LENGTH_ERR;
+                }
+                else {
+                    transResult = tCnt + 1;     /* 전이 완료 */
+                }
+                return transResult;
+            }
+            /* 12. 전이가 한번에 끝나지 않으면 일단 1회 최대 전이량(Positive 면 133%, Negative 이면-17%)
+             * 전이 주기 저장 후 계산 시각 (aClock)을 그만큼 이동
+             */
+            else {
+                /* 음의 방향 전이  이면 줄여서 전이 */
+                if (input.transDir == TTod.NEGATIVE) {
+                    transLength = (int) (tCycle * 0.83);
+                }
+                /* 양의 방향 전이 이면 늘려서 전이 */
+                else {
+                    transLength = (int) (tCycle * 1.34);
+                }
+
+                /* 현시 맞추어 늘림 */
+                if (stretchSplit(out.get(tCnt), transLength, tCycle, input) < 0) {
+                    transResult = TTod.CYCLE_LENGTH_ERR;
+                    return transResult;
+                }
+                aClock += transLength;
+            }
+        }
+
+        /* 허용 전이 횟수 까지 루프가 돌고 break 되면 전이 횟수 초과 에러 */
+        transResult = TTod.EXCEED_MAXTRANS;
+        return transResult;
+    }
+
+
+}