Skip to content

Commit 8877dec

Browse files
authored
Merge pull request #130 from SEMOSAN/feat/#127-course-altitude-stats
[feat] 코스 응답에 누적 상승/하강/최고 고도 추가
2 parents 7a99b57 + 77a672a commit 8877dec

5 files changed

Lines changed: 68 additions & 0 deletions

File tree

src/main/java/com/semosan/api/domain/mountain/dto/response/CourseDetailResponse.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ public record CourseDetailResponse(
1818
Integer duration,
1919
String startName,
2020
String endName,
21+
Double ascent,
22+
Double descent,
23+
Double maxAltitude,
2124
@JsonRawValue String polyline,
2225
@JsonRawValue String altitudes
2326
) {
@@ -30,6 +33,9 @@ public static CourseDetailResponse from(CourseDetailProjection p) {
3033
p.getDuration(),
3134
p.getStartName(),
3235
p.getEndName(),
36+
p.getAscent(),
37+
p.getDescent(),
38+
p.getMaxAltitude(),
3339
p.getPolyline(),
3440
p.getAltitudes()
3541
);

src/main/java/com/semosan/api/domain/mountain/entity/Course.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ public class Course extends BaseEntity {
4747
@Column(name = "end_name", length = 100)
4848
private String endName;
4949

50+
/** 누적 상승 고도(m). Σ max(0, current - previous). altitudes 없거나 점 1개 이하면 null. */
51+
@Column(name = "ascent")
52+
private Double ascent;
53+
54+
/** 누적 하강 고도(m). Σ max(0, previous - current). altitudes 없거나 점 1개 이하면 null. */
55+
@Column(name = "descent")
56+
private Double descent;
57+
58+
/** 코스 최고 고도(m). altitudes 의 max. null 가능. */
59+
@Column(name = "max_altitude")
60+
private Double maxAltitude;
61+
5062
@Column(name = "polyline", columnDefinition = "geography(LineString, 4326)")
5163
private LineString polyline;
5264

src/main/java/com/semosan/api/domain/mountain/repository/CourseRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public interface CourseRepository extends JpaRepository<Course, Long> {
3535
c.duration AS duration,
3636
c.start_name AS startName,
3737
c.end_name AS endName,
38+
c.ascent AS ascent,
39+
c.descent AS descent,
40+
c.max_altitude AS maxAltitude,
3841
ST_AsGeoJSON(c.polyline)::text AS polyline,
3942
c.altitudes::text AS altitudes
4043
FROM courses c

src/main/java/com/semosan/api/domain/mountain/repository/projection/CourseDetailProjection.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ public interface CourseDetailProjection {
1313
Integer getDuration();
1414
String getStartName();
1515
String getEndName();
16+
/** 누적 상승 고도(m). altitudes 없거나 점 1개 이하면 null. */
17+
Double getAscent();
18+
/** 누적 하강 고도(m). altitudes 없거나 점 1개 이하면 null. */
19+
Double getDescent();
20+
/** 코스 최고 고도(m). null 가능. */
21+
Double getMaxAltitude();
1622
/** GeoJSON LineString 문자열 (ST_AsGeoJSON 결과). null 가능. */
1723
String getPolyline();
1824
/** 점별 고도 배열 jsonb 문자열. null 가능. */
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
-- 코스 누적 상승/하강 고도 + 최고 고도 컬럼 추가.
2+
-- altitudes(jsonb 숫자 배열) 를 풀어 다음 정의로 백필한다.
3+
-- ascent = Σ max(0, current - previous)
4+
-- descent = Σ max(0, previous - current)
5+
-- max_altitude = max(altitudes)
6+
-- altitudes 가 null 이거나 점이 1개 이하인 코스는 셋 다 NULL 로 남김.
7+
8+
ALTER TABLE courses
9+
ADD COLUMN ascent DOUBLE PRECISION,
10+
ADD COLUMN descent DOUBLE PRECISION,
11+
ADD COLUMN max_altitude DOUBLE PRECISION;
12+
13+
WITH altitude_pairs AS (
14+
SELECT
15+
c.id AS course_id,
16+
(a.value #>> '{}')::numeric AS altitude,
17+
LAG((a.value #>> '{}')::numeric)
18+
OVER (PARTITION BY c.id ORDER BY a.ordinality) AS prev_altitude
19+
FROM courses c
20+
CROSS JOIN LATERAL
21+
jsonb_array_elements(c.altitudes) WITH ORDINALITY AS a(value, ordinality)
22+
WHERE c.altitudes IS NOT NULL
23+
AND jsonb_array_length(c.altitudes) > 1
24+
),
25+
calculated AS (
26+
-- prev_altitude IS NULL 인 첫 점도 포함시켜 MAX(altitude) 가 모든 점을 보게 한다.
27+
-- ascent/descent 의 GREATEST(0, x - NULL) 은 PostgreSQL 이 NULL 을 무시해 0 으로 처리하므로 SUM 결과에 영향 없음.
28+
SELECT
29+
course_id,
30+
SUM(GREATEST(0, altitude - prev_altitude)) AS ascent,
31+
SUM(GREATEST(0, prev_altitude - altitude)) AS descent,
32+
MAX(altitude) AS max_altitude
33+
FROM altitude_pairs
34+
GROUP BY course_id
35+
)
36+
UPDATE courses c
37+
SET ascent = calc.ascent,
38+
descent = calc.descent,
39+
max_altitude = calc.max_altitude
40+
FROM calculated calc
41+
WHERE c.id = calc.course_id;

0 commit comments

Comments
 (0)